一、对象的分类
instance对象
实例对象就是通过alloc出来的对象,每次调用alloc都会产生新的instance对象;
实例对象的存储信息
isa指针
其他成员变量
class对象
每个类的内存中有且只有一个类对象
类对象的存储信息
isa指针
superClass指针
类的属性信息(@property),类的对象方法信息(method),类的协议信息(protocol),类的成员变量信息(ivar)
类对象是由程序员定义并在运行时由编译器创建的,它没有自己的实例变量,这里需要注意的是类的成员变量和实例方法列表是属于实例对象的,但其存储于类对象当中的。
每个类的内存中有且只有一个元类对象
元类的存储信息
isa指针
superClass指针
类的属性信息(@property),类的对象方法信息(method),类的协议信息(protocol),类的成员变量信息(ivar)
元类和类的存储结构是一样的,但是用途不一样
instance的isa指向class对象(类对象),当调用对象方法时,通过instance的isa找到class对象(类对象),最后找到对象方法的实现进行调用;
class的isa指向meta-class,当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用;
objective-c源码中对对象的定义是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 typedef struct objc_object *id ;struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; ---------------------------------------------------- typedef struct objc_class *Class;struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; 这里也是有个isa 指针,其实这个isa 指向的就是 **元类** ----------------------------------------------------
这里介绍一下元类,介绍之前先理解以下概念
类对象 == object_getClass(实例对象) == [实例对象 class] == [类对象 class]
object_getClass(类对象) == 类对象的isa == 元类
object_getClass(类对象) != [类对象 class]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @interface Test : NSObject @property (nonatomic , assign ) int p1;@end @implementation Test @end int main(int argc, const char * argv[]) { @autoreleasepool { Test *test = [[Test alloc] init]; Class cls1 = test.class; Class cls2 = object_getClass(test); Class cls3 = Test.class; Class cls4 = [cls1 class ]; NSLog (@"cls1:%p cls2:%p cls3:%p cls4:%p" , cls1, cls2, cls3, cls4); Class cls5 = object_getClass(cls1); Class cls6 = objc_getMetaClass(object_getClassName(test)); NSLog (@"cls5:%p cls6:%p" , cls5, cls6); const char *cls2_name = object_getClassName(cls2); const char *cls6_name = object_getClassName(cls6); NSLog (@"cls2_name:%s cls6_name:%s" , cls2_name, cls6_name); } return 0 ; }
很奇怪,为什么cls6_name
是NSObject
,通过object_getClassName
的源码可以知道,这个方法获取的是isa指向的名称。
1 2 3 4 5 const char *object_getClassName(id obj){ return class_getName(obj ? obj->getIsa() : nil ); }
总结一下实例对象,类对象以及元类对象之间的isa指向和继承关系的规则为:
规则一: 实例对象的isa指向该类,类的isa指向元类(metaClass)
规则二: 类的superClass指向其父类,如果该类为根类则值为nil
规则三: 元类的isa指向根元类,如果该元类是根元类则指向自身
规则四: 元类的superClass指向父元类,若根元类则指向该根类
三、实践
实践一
考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @interface Sark : NSObject @end @implementation Sark @end int main(int argc, const char * argv[]) { @autoreleasepool { BOOL res1 = [(id )[NSObject class ] isKindOfClass:[NSObject class ]]; BOOL res2 = [(id )[NSObject class ] isMemberOfClass:[NSObject class ]]; BOOL res3 = [(id )[Sark class ] isKindOfClass:[Sark class ]]; BOOL res4 = [(id )[Sark class ] isMemberOfClass:[Sark class ]]; NSLog (@"%d %d %d %d" , res1, res2, res3, res4); } return 0 ; }
先来分析一下源码这两个函数的对象实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 + (Class)class { return self ; } - (Class)class { return object_getClass(self ); } Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } inline Class objc_object::getIsa() { if (isTaggedPointer()) { uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; return objc_tag_classes[slot]; } return ISA(); } inline Class objc_object::ISA() { assert(!isTaggedPointer()); return (Class)(isa.bits & ISA_MASK); } + (BOOL )isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id )self ); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES ; } return NO ; } - (BOOL )isKindOfClass:(Class)cls { for (Class tcls = [self class ]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES ; } return NO ; } + (BOOL )isMemberOfClass:(Class)cls { return object_getClass((id )self ) == cls; } - (BOOL )isMemberOfClass:(Class)cls { return [self class ] == cls; }
首先题目中NSObject 和 Sark分别调用了class方法。
+ (BOOL)isKindOfClass:(Class)cls
方法内部,会先去获得object_getClass
的类,而object_getClass
的源码实现是去调用当前类的obj->getIsa()
,最后在ISA()方法中获得meta class
的指针。
接着在isKindOfClass
中有一个循环,先判断class
是否等于meta class
,不等就继续循环判断是否等于super class
,不等再继续取super class
,如此循环下去。
[NSObject class]
执行完之后调用isKindOfClass
,第一次判断先判断NSObject
和 NSObject
的meta class
是否相等,之前讲到meta class
的时候放了一张很详细的图,从图上我们也可以看出,NSObject
的meta class
与本身不等。接着第二次循环判断NSObject与meta class
的superclass
是否相等。还是从那张图上面我们可以看到:Root class(meta)
的superclass
就是 Root class(class)
,也就是NSObject
本身。所以第二次循环相等,于是第一行res1输出应该为YES。
同理,[Sark class]
执行完之后调用isKindOfClass
,第一次for循环,Sark
的Meta Class
与[Sark class]
不等,第二次for循环,Sark Meta Class
的super class
指向的是 NSObject Meta Class
, 和 Sark Class
不相等。第三次for循环,NSObject Meta Class
的super class
指向的是NSObject Class
,和 Sark Class
不相等。第四次循环,NSObject Class
的super class
指向 nil, 和 Sark Class
不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。
如果把这里的Sark改成它的实例对象,[sark isKindOfClass:[Sark class]
,那么此时就应该输出YES了。因为在isKindOfClass
函数中,判断sark
的isa
指向是否是自己的类Sark
,第一次for循环就能输出YES了。
isMemberOfClass
的源码实现是拿到自己的isa指针和自己比较,是否相等。
第二行isa 指向NSObject
的 Meta Class
,所以和 NSObject Class
不相等。第四行,isa指向Sark的Meta Class
,和Sark Class
也不等,所以第二行res2和第四行res4都输出NO。
实践二
考虑以下代码是否可以正常运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @interface Sark : NSObject @property (nonatomic , copy ) NSString *name;- (void )speak; @end @implementation Sark - (void )speak { NSLog (@"my name is aaa" ); } @end int main(int argc, const char * argv[]) { @autoreleasepool { id cls = [Sark class ]; void *obj = &cls; id obj_new = (__bridge id )obj; [obj_new speak]; size_t cls_size = class_getInstanceSize(Sark.class); size_t obj_size = malloc_size(obj); NSLog (@"cls_size:%zu, obj_size:%zu" , cls_size, obj_size); } return 0 ; }
答案是可以的,因为obj是指向类对象Sark的指针,将obj转为iOS对象obj_new,因为iOS对象的前8个字节是isa指针,指向类对象Sark,这里obj_new就相当于在内存中只占用8个字节的栈对象,所以是可以进行方法调用的,但是由于是栈对象,所以obj_size的值是0;
所以输出的结果如下
2021-04-12 00:06:13.596537+0800 ClassTest[6501:1363097] my name is aaa
2021-04-12 00:06:13.596620+0800 ClassTest[6501:1363097] cls_size:16, obj_size:0
参考
https://halfrost.com/objc_runtime_isa_class/