上一篇iOS 底层探究之alloc研究了NSObject对象实例化时alloc方法实现内存分配和isa绑定。我们都知道实例对象的isa指向类对象,我们这节就研究isa的指向问题。
isa指向问题
我们都知道对象的isa指向类对象地址,类对象的isa指向元类对象地址,非NSObject的元类对象的isa指向NSObject的元类地址。关于meta类型,只有在一些类型判断,和metaclassNSObject查阅到,如下:
static Class metaclassNSObject(void)
{
extern objc_class OBJC_METACLASS_$_NSObject;
return (Class)&OBJC_METACLASS_$_NSObject;
}
bool isMeta = cls->isMetaClass();
下面我们来证明isa的指向问题。
每个对象都有元类对象
1.创建一个LKXPerson对象,然后通过lldb获取元类对象
@interface LKXPerson : NSObject
@end
@implementation LKXPerson
@end
LKXPerson *person = [LKXPerson new];
Class personCls = [LKXPerson class];
Class metaPersonCls = object_getClass(personCls);
2.获取person对象的isa指针,person的前8个字节就存储的isa指针、
(lldb) x/4gx person
0x101e040e0: 0x011d800100008771 0x0000000000000000
0x101e040f0: 0x0000000000000000 0x0000000000000000
3.objc4源码 isa.h中描述了获取类对象地址需要通过掩码获取, 下面是64位获取类对象地址的掩码ISA_MASK。
# define ISA_MASK 0x00007ffffffffff8ULL
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
4.对isa进行对掩码做&-与操作,获取类对象地址,然后再po类对象地址,获取其类型, 获取到类对象类型名称是LKXPerson
(lldb) p/x 0x011d800100008771 & 0x00007ffffffffff8
(long) $6 = 0x0000000100008770
(lldb) po $6
LKXPerson
5.类对象继承于对象
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
}
6.因为类对象继承对象对象,所以它也有一个isa指针,可以通过对象获取类对象地址一样,获取元类对象地址
(lldb) x/4gx $6
0x100008770: 0x0000000100008748 0x000000010036e140
0x100008780: 0x0000000101e04100 0x0001801000000003
(lldb) p/x 0x0000000100008748 & 0x00007ffffffffff8
(long) $7 = 0x0000000100008748
(lldb) po $7
LKXPerson
7.但是有一个问题,类对象(0x0000000100008770)和元类对象(0x0000000100008748)是同一个名称,但是是不同的地址, 这是什么原因呢?想到我们代码中的所有代码都需要转成Symbols,所以我通过MachOViews查看符号化,发现其实有两个对象,一个是 _OBJC_METACLASS_ROLKXPerson, 一个是 __OBJC_CLASS_RO_LKXPerson,所以我猜想,类对象和元类对象的description打印是对$做了分割,打印最后一部分。
在objc4的ReleaseNotes.rtf中描述, class符号化名称时来之_OBJC_CLASS__ClassName 和 _OBJC_METACLASS__ClassName,元类使用子类的符号,所以类对象和元类对象的名称都是一样的。
Class symbols have names of the form _OBJC_CLASS_$_ClassName and _OBJC_METACLASS_$_ClassName . The class symbol is used by clients who send messages to the class (i.e. [ClassName someMessage]). The metaclass symbol is used by clients who subclass the class.
8.类对象是一个对象,那么元类对象也是一个对象,那么元类对象的isa指向什么呢?
(lldb) x/4gx $7
0x100008748: 0x000000010036e0f0 0x000000010036e0f0
0x100008758: 0x0000000100646d60 0x0002e03100000003
(lldb) p/x 0x000000010036e0f0 & 0x00007ffffffffff8
(long) $3 = 0x000000010036e0f0
(lldb) po $3
NSObject
9.我们发现元类的isa指向NSObject,粗略一看怎么有点死循环。细想一下,此“NSObject”非彼“NSObject”,这里的的NSObject是元类对象,我们获取元类对象的内存地址和它对比,发现一致, 都是0x000000010036e0f0。
Class objCls = [NSObject class];
Class metaObjCls = object_getClass(objCls);
(lldb) p metaObjCls
(Class) $5 = 0x000000010036e0f0
9.NSObject元类对象的isa指向哪里呢? 发现指向它自己。
(lldb) x/4gx $5
0x10036e0f0: 0x000000010036e0f0 0x000000010036e140
0x10036e100: 0x0000000101e041c0 0x0003e03100000007
(lldb) p/x 0x000000010036e0f0 & 0x00007ffffffffff8
(long) $6 = 0x000000010036e0f0
10.无论是OBJC_METACLASS__NSObject还是__OBJC_METACLASS_RO_LKXPerson,我们都没有看到其声明和实现,而且__OBJC_METACLASS_RO_$LKXPerson是我们创建对象的元类对象,说明这个应该是runtime帮我们自动生成的。
11.这一部分已经验证了isa的指向: 对象的isa指向类对象,类的isa指向元类,非根类NSObject的元类isa指向根类NSObject的元类,根类NSObject的元类的isa指向根类NSObject元类自己。
superClass指向
我们都知道superClass指向父类,但是通过对isa的指向问题,发现分类和非根类的指向都有所区别。下面我们来研究下superClass的指向。
- 首先创建两个类,LKXTeacher继承于LKXPerson,继承与NSObject,参考如下代码:
NSObject *obj = [NSObject new];
Class objCls = [NSObject class];
Class metaObjCls = object_getClass(objCls);
LKXPerson *person = [LKXPerson new];
Class personCls = [LKXPerson class];
Class metaPersonCls = object_getClass(personCls);
LKXTeacher *teahcer = [LKXTeacher new];
Class teacherCls = [LKXTeacher class];
Class metaTeacherCls = object_getClass(teacherCls);
NSLog(@"NSObject superClass: %p - %p - %p", obj.superclass, [objCls superclass], [metaObjCls superclass]);
NSLog(@"LKXPerson superClass: %p - %p - %p", person.superclass, [personCls superclass], [metaPersonCls superclass]);
NSLog(@"LKXTeacher superClass: %p - %p - %p", teahcer.superclass, [teacherCls superclass], [metaTeacherCls superclass]);
- 以下是各个对象及其superClass对象的地址。
NSObject : 0x1018b0c70 - 0x10036e140 - 0x10036e0f0
LKXPerson : 0x1018afd50 - 0x100008778 - 0x100008750
LKXTeacher: 0x1018af710 - 0x1000087c8 - 0x1000087a0
NSObject superClass: 0x0 - 0x0 - 0x10036e140
LKXPerson superClass: 0x10036e140 - 0x10036e140 - 0x10036e0f0
LKXTeacher superClass: 0x100008778 - 0x100008778 - 0x100008750
- NSObject对象和类对象的superClass对象都指向空0x0,这个很好理解,因为NSObject类是根类,所以,它的实例对象和它自己是没有父类的。
- NSObject的元类的superClass和NSObject类对象地址都是 0x10036e140,所以NSObject的元类对象的父类是NSObject,这好像不合理,但是却也是合理的。因为在iOS中,NSObject是根类,所以类都必须继承与它,所以它的元类也需要遵守。
- LKXPerson的实例对象和类对象的superClass都指向NSObject类地址0x10036e140,且LKXTeacher的实例对象和类对象的superClass都指向LKXPerson类地址 0x100008778,所以验证了非根类NSObject的实例对象和类对象指向其父类对象。
- LKXTeacher元类的superClass指向LKXPerson元类对象地址0x100008750, LKXPerson元类的superClass指向根类NSObject元类地址0x10036e0f0,所以验证非根类NSObject的元类对象的superClass指向其类独享父类的元类对象。
- superClass的地址研究,我们可以得到:
- 非NSObject实例对象和类对象的superClass指向其类对象父类,非NSObject元类对象的superClass指向其类对象父类的元类对象。
- NSObject实例对象和类对象的superClass指向nil,NSObject的元类对象的superClass指向NSObject类对象自己。
总结
通过对isa指针指向和superClass指针指向的验证,我们可以总结一下两点和经典图解。
- 对象的isa指向类对象,类的isa指向元类,非根类NSObject的元类isa指向根类NSObject的元类,根类NSObject的元类的isa指向根类NSObject元类自己。
- superClass:
- 非NSObject实例对象和类对象的superClass指向其类对象父类,非NSObject元类对象的superClass指向其类对象父类的元类对象。
- NSObject实例对象和类对象的superClass指向kong,NSObject的元类对象的superClass指向NSObject类对象自己。
- 经典的isa和superClass指针指向图解: