1、元类
- 我们都知道对象的isa是指向类,类其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类
- 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
- 元类是类对象的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。
- 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
关系链:对象 --> 类 --> 元类 --> NSobject,NSObject指向自身
2、NSObject到底有几个?
最后NSObject类的元类也是NSObject,内存中只存在一份根元类NSObject,根元类的元类是指向它自己
[面试题]:类存在几份?
由于类的信息在内存中永远只存在一份,所以类对象只有一份
3、著名的isa走位和继承关系图
isa走位
- 实例对象(Instance of Subclass)的isa指向类(class)
- 类对象(class)isa指向元类(Meta class)
- 元类(Meta class)的isa指向根元类(Root metal class)
- 根元类(Root metal class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject
superclass走位(即继承关系)
类之间的继承关系:
- 类(subClass)继承自父类(superClass)
- 父类(superClass)继承自根类(RootClass),此时的根类是指NSObject
- 根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
元类也存在继承,元类之间的继承关系如下:
- 子类的元类(metal SubClass)继承自父类的元类(metal SuperClass)
- 父类的元类(metal SuperClass)继承自根元类(Root metal Class
- 根元类(Root metal Class)继承于根类(Root class),此时的根类是指NSObject
注意:实例对象之间没有继承关系,类之间有继承关系
4、objc_class & objc_object
为什么对象和类都有isa属性呢?这里就不得不提到两个结构体类型:objc_class & objc_object
NSObject的底层编译是NSObject_IMPL结构体,其中 Class是isa指针的类型,是由objc_class定义的类型,而objc_class是一个结构体。在iOS中,所有的Class都是以 objc_class 为模板创建的
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
objc_class 与 objc_object 有什么关系?
- 结构体类型objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
- mian.cpp底层编译文件中,NSObject中的isa在底层是由Class定义的,其中class的底层编码来自objc_class类型,所以NSObject也拥有了isa属性
- NSObject是一个类,用它初始化一个实例对象objc,objc满足objc_object的特性(即有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_object,objc_object有isa属性。所以对象都有一个isa,isa表示指向,来自于当前的objc_object
- objc_object(结构体)是当前的根对象,所有的对象都有这样一个特性objc_object,即拥有isa属性
objc_object 与对象的关系
- 所有的对象都是以objc_object为模板继承过来的
- 所有的对象是来自NSObject(OC),但是真正到底层的是一个objc_object(C/C++)的结构体类型
5、方法列表存储位置
- 类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如CJLPersong类的实例方法sayHello就存储在CJLPerson类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法和get方法
- 类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如CJLPerson中的类方法sayBye就存储在CJLPerson类的元类(名称也是CJLPerson)的bits属性中
6、什么是属性、成员变量、实例变量?
- 属性(property):在OC中是通过@property开头定义,且是带下划线成员变量 + setter + getter方法的变量
- 成员变量(ivar):在OC的类中{}中定义的,且没有下划线的变量
- 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量,例如NSObject、UILabel、UIButton等
7、元类中为什么会有类对象的类方法?
lgObjc_copyMethodList函数分析
在这个函数中,主要是获取类中的方法列表,实例方法存储在类中,类方法存储在元类中
lgInstanceMethod_classToMetaclass函数分析
在分析前,需要先了解class_getInstanceMethod这个方法,主要是用于获取实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
lgClassMethod_classToMetaclass函数分析
在分析前,需要先了解class_getClassMethod这个方法,主要是用于获取类方法,如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//获取元类
// NOT identical to this->ISA when this is a metaclass 判断是否是元类,是元类就直接返回,反之,继续找isa指向
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
看该方法的源码实现,可以得出class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod,但是在这里需要注意的一点是:在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
源码流程图如下所示
lgIMP_classToMetaclass函数分析
class_getMethodImplementation 主要是返回方法的具体实现
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法实现
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
//如果没有找到,则进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分
总结:
- class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL
- class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。
- class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发