1、类存在几份?
由于类的信息在内存中永远只存在一份,所以 类对象只有一份,同样,元类对象也只有一份,在lldb中po类对象和元类对象的地址会输出相同的类名,因为po会调用类的description方法。
2、objc_object 与 对象的关系
objc_object是OC类的c/c++实现,没有直接的联系,编译器会在编译阶段将OC语法的类转译为c/c++的objc_object结构体实现,objc_object结构体是OC类的底层模板。通过typedef struct objc_object *id;定义可以看到id这个可以指向任意OC类关键字实际是指向objc_object结构体的指针。
3、什么是 属性 & 成员变量 & 实例变量 ?
- 属性在OC中是通过
@property开头定义在类的头文件中,编译器会给这样声明的属性添加set和get方法,另外在类内部也可以通过_ivarName的方式,即下划线加属性名的方式直接访问属性,而不必使用点语法,这样做效率更高,减少了set和get方法的消息转发。 - 成员变量在OC的类
@implement{}中定义的,仅供类内部访问的变量,编译器不会生成set和get方法。 - 实例变量是对象类型的变量,是需要实例化的变量,是一种特殊的成员变量,例如 NSObject、UILabel、UIButton等。
4、如何证明元类中存放着类方法
//定义Person类
@interface Person : NSObject
- (void)sayHi;
+ (void)sayHaHa;
@end
//添加类方法和实例方法
#import "Person.h"
@implementation Person
- (void)sayHi{
NSLog(@"Person say : Hi!!!");
}
+ (void)sayHaHa{
NSLog(@"Person say : HaHa!!!");
}
@end
通过运行时方法class_copyMethodList可以获取一个类的实例的方法列表:
//打印方法名
void objc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
CHLog(@"Method, name: %@", key);
}
free(methods);
}
在main中运行以上代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
objc_copyMethodList(pClass);
}
return 0;
}
输出结果:

在
objc_copyMethodList法中我们传入了Person类对象,所以只会输出类的实例方法sayHi,符合预期,再看下面的方法输出:
void instanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHi));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHi));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHaHa));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHaHa));
CHLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
这个方法分别针对Person类和元类调用class_getInstanceMethod方法,看看打印结果:

可以看到共打印了4个方法的地址,只有
method1和method4打印出了内容,其余都是0x0说明没找到对应的方法,这就很好的证明了实例方法sayHi可以在类对象pClass中找到,而类方法sayHaHa,只能在metaClass元类中找到,这4个方法都调用了class_getInstanceMethod,从方法名中可以判断是获取实例方法,进而说明对元类调用获取实例方法,就相当于获取一个类的类方法,因为类在内存中也是一个特殊的实例,元类中存储的就是这个特殊实例的实例方法。
void classMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHi));
Method method2 = class_getClassMethod(metaClass, @selector(sayHi));
Method method3 = class_getClassMethod(pClass, @selector(sayHaHa));
Method method4 = class_getClassMethod(metaClass, @selector(sayHaHa));
CHLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
将上面方法中调用的class_getInstanceMethod换成class_getClassMethod,分别对类和元类进行测试,看看打印结果:

打印结果只有
method3和method4打印出了地址,分析之前看看class_getClassMethod的源码实现:
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//获取一个类的元类,如果本身是元类,就直接返回自己,否则返回类的ISA()找到元类
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
-
method1是传入了Person类本身和实例方法sayHi,把参数带入到class_getClassMethod中分析,经过第一个if (!cls || !sel) return nil;条件判断,最终调用了class_getInstanceMethod传入了Person类的元类,sel参数是sayHi,这个方法在Person元类中是找不到的,因为它是一个实例方法,存放在类对象中,所以会返回0x0,找不到这个方法。 -
method2是传入了Person类的元类和实例方法sayHi,把参数带入到class_getClassMethod中分析,经过条件判断,最终调用了class_getInstanceMethod传入了Person类的元类,sel参数是sayHi,元类的getMeta()方法会返回自己,所以还是在Person元类中查找sayHi,因为它是一个实例方法,存放在类对象中,元类中没有这个方法,返回0x0,找不到这个方法。 -
method3是传入了Person类本身和类方法sayHaHa,把参数带入到class_getClassMethod中分析,最终调用了class_getInstanceMethod传入了Person类的元类,sel参数是sayHaHa,这个方法在Person元类中是存在的,因为sayHaHa是一个类方法,返回方法地址0x100003148。 -
method4是传入了Person类的元类和类方法sayHaHa,把参数带入到class_getClassMethod中,最终调用了class_getInstanceMethod传入了Person类的元类,sel参数是sayHaHa,元类的getMeta()方法会返回自己,所以还是在Person元类中查找类方法sayHaHa,可以找到方法并返回方法地址0x100003148。
为什么元类的getMeta()会返回自己,这个是为了防止无限递归,因为对一个类调用class_getClassMethod会顺着类的isa找到类的元类,而对元类调用class_getClassMethod的话,如果不返回元类自身,还是沿着元类的isa查找,最终会找到根元类,所有元类的isa都指向根元类,而根元类的isa指向了自己,就会在这里形成死循环,为了打破这个循环,就在元类的getMeta()方法中直接返回自己终止isa的循环。
再来看看下面的方法:
void imp_ClassToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHi;
// + (void)sayHaHa;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHi));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHi));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHaHa));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHaHa));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
结果打印:0x100001d10-0x7fff6a7d6580-0x7fff6a7d6580-0x100001d40,四个imp都会打印出来,不太符合直觉,从元类里面获取实例方法的实现也拿到了地址,从类里面获取类方法的实现也能拿到地址,看看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;
}
从源码中可以知道由于消息转发机制的处理,即使类或者元类中找不到对应的类方法或者实例方法,最终通过消息转发,也能获取到对应方法的实现!
5、分析下面代码中iskindOfClass 、 isMemberOfClass 的打印结果:
void isKindAndisMember() {
//-----调用类的 iskindOfClass & isMemberOfClass
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]]; //
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]]; //
NSLog(@"\nClass isKindAndisMember\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
//------调用实例的iskindOfClass & isMemberOfClass
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]]; //
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]]; //
NSLog(@"\nInstance isKindAndisMember\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
看下打印结果:

分析前,先看看
iskindOfClass 和isMemberOfClass的源码:
//isKindOfClass类方法
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//isKindOfClass实例方法
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//isMemberOfClass类方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//isMemberOfClass实例方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
下面开始分析:
-
res1=[(id)[NSObject class] isKindOfClass:[NSObject class]];
对NSObject调用isKindOfClass,首先会调用+ (BOOL)isKindOfClass:(Class)cls类方法,其过程是for循环中tcls初始等于self->ISA(),也就是NSObject的元类,第一次循环相当于比较NSObject类和NSObject的元类是否地址相同,显然不相等,进入下一循环,此时tcls等于它的父类tcls->superclass,NSObject元类就是根源类,根源类的superclass指向NSObject类,此时tcls就变成NSObject类,再次比较tcls和传入的cls,这两个值现在都是NSObject类,所以是相等的,返回YES, 所以res1打印结果就是1。 -
re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
对NSObject调用isMemberOfClass,会调用+ (BOOL)isMemberOfClass:(Class)cls类方法,最终比较的是self->ISA()和传入的cls,也就是NSObject的元类和NSObject类,显然实不相等的,所以返回NO,re2打印为0 -
re3 = [(id)[Person class] isKindOfClass:[Person class]];
对Person调用isKindOfClass,首先会调用+ (BOOL)isKindOfClass:(Class)cls类方法,for循环中tcls初始等于self->ISA(),也就是Person的元类,相当于比较Person类和Person的元类是否地址相同,不相等,进入下一循环,此时tcls等于它的父类tcls->superclass,也就是NSObject元类,是根源类,和Person类也不相等,继续向上寻找superclass,下一个循环中tcls就变成了NSObject类,根源类的superclass是NSObject类,和Person类也不相等,继续再向上找superclass,NSObject类的superclass是nil,和Person类也不相等,最后由于tcls变成了nil循环终止,返回NO,所以打印re3也是0 -
re4 = [(id)[Person class] isMemberOfClass:[Person class]];
对Person调用isMemberOfClass,会调用Person类的+ (BOOL)isMemberOfClass:(Class)cls类方法,最终比较的是self->ISA()和传入的cls,也就是Person的元类和Person类,显然实不相等的,所以返回NO,re4打印为0 -
re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
对NSObject的实例调用isKindOfClass,会调用NSObject类的- (BOOL)isKindOfClass:(Class)cls实例方法,for循环中tcls初始等于[self class],也就是NSObject类,传入的参数cls是[NSObject class],也是NSObject类,所以比较tcls和cls是相等的,返回YES,打印re5是1 -
re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
对NSObject的实例调用isMemberOfClass,会调用NSObject类的- (BOOL)isMemberOfClass:(Class)cls实例方法,比较的是[self class]和传入的[NSObject class]是否相等,显然是相等的,都是NSObject类,打印re6是1 -
re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
对Person的实例调用isKindOfClass,会调用Person类的- (BOOL)isKindOfClass:(Class)cls实例方法,for循环中tcls初始等于[self class],也就是Person类,传入的参数cls是[Person class],也是Person类,所以比较tcls和cls是相等的,返回YES,打印re7是1 -
re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];
对Person的实例调用isMemberOfClass,会调用Person类的- (BOOL)isMemberOfClass:(Class)cls实例方法,比较的是[self class]和传入的[Person class]是否相等,显然是相等的,都是Person类,打印re8是1
坑点:
上面的分析似乎顺利成章,看不出破绽,都是对着isKindOfClass和isMemberOfClass的代码实现一步一步分析的,而且分析的结果也是正确的,但是如果我们用断点调试的话就会发现这里面是有坑的:


分别在以上位置下断点,单步运行就会发现不论是实例方法
isKindOfClass还是类方法isKindOfClass都没进到对应的断电中,而isMemberOfClass都可以进入对应的方法,这个结果是不是很意外,难道我们上面分析的isKindOfClass流程都是错的吗,系统没有进入对应的isKindOfClass方法,那最终调用的又是那个方法呢?用汇编方式来看看最终调用的方法是什么:
如上图在
Debug菜单选择Always Show Disassembly显示整个方法的汇编代码:
从图中标记
1的红框可以看到,实际的isKindOfClass方法调用已经变成了objc_opt_isKindOfClass,而标记2的红框显示isMemberOfClass依然是消息转发objc_msgSend方式去调用,所以isMemberOfClass可以进入对应的方法断点,而isKindOfClass已经直接调用objc_opt_isKindOfClass的实现了,这里应该是llvm做的编译优化。在runtime源码中搜索一下objc_opt_isKindOfClass方法看看是否能找到这个方法:
很容易就搜索到了,声明在
objc-internal.h中,实现在NSObject.mm中,点击查看方法实现:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
可以看到最上面的注释中已经说明了这个就是调用[obj isKindOfClass]方法最终所执行的实现,在这个方法中打断点可以看到,不论是调用实例的isKindOfClass还是类的isKindOfClass,都会进入这个方法,这个方法和之前我们认为要走的+ (BOOL)isKindOfClass:(Class)cls和- (BOOL)isKindOfClass:(Class)cls的实现基本是一致的,略有不同,分析一下执行过程:
-
re1:re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
进入objc_opt_isKindOfClass(id obj, Class otherClass)方法后,obj是NSObject类对象,后面然后通过Class cls = obj->getIsa();取得NSObject类的元类,之后进入for循环,这里就和+ (BOOL)isKindOfClass:(Class)cls的流程一致了,最终通过根源类的superclass判断相等
同理,re3 = [(id)[Person class] isKindOfClass:[Person class]];的过程也是如此,此处就不再赘述了。
再看实例对象调用objc_opt_isKindOfClass(id obj, Class otherClass)的情况: -
re5:re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
此时objc_opt_isKindOfClass方法的obj参数变成了NSObject的实例,通过Class cls = obj->getIsa();取得NSObject类对象,进入for循环,此时tcls是NSObject类对象,另一个参数otherClass也是NSObject类对象,这两个必然是相等的,返回YES, 所以re5打印是1