OC对象的本质(中):OC对象的种类
github
先总结一下咱们在对象的分类一文里面分析过的问题,OC对象氛围三类面试
instance对象
,内部包含markdown
class对象
,用来描述instance对象,内部包含app
meta-class对象
,用来存放类方法,内部包含ide
对一个类来讲,它的instance
、class
、mete-class
对象之间,必定是有某种联系的。假设这种联系不存在,咱们看看会碰到什么问题。好比我调用一个instance对象的方法oop
[InstanceObj InstanceObjMethod];
复制代码
它的底层是post
objc_msgSend(instanceObj, @sel_registerName("instanceObjMethod"));
复制代码
也就是给instanceObj对象发消息。ui
在instance对象
不跟外界关联的状况下,它内部只有一些成员变量信息,是不可能完成方法调用的,由于对象方法是存放在class对象
里面的,对class对象
调用+方法
的时候也是同样,必须有办法跟meta-class对象
关联起来,才能完成对+方法
的调用。因此isa指针,就只它们之间的关联。能够经过下图来理解isa的做用。spa
大体能够概括为
instance对象
的isa
指针指向class对象
。当调用对象方法(-方法)时,经过instance
的isa
找到class
,而后在class
的方法列表里面找到对应的实现进行调用。class对象
的isa指针指向meta-class对象
。当调用类方法(+)方法时,经过class
的isa
找到meta-class
,最后在meta-class
的方法列表找到对应的实现进行调用。那么经过isa的桥接做用,我梦应该能更近一步地理解OC消息发送以及方法调用的过程了。
显而易见,从字面意思,咱们就能知道,superclass就是父类的意思。 假定咱们有如下几个类
@interface Person : NSObject
@end
@interface Student : Person
@end
复制代码
咱们知道superclass
指针存在于class对象
和meta-class对象
里面。咱们根据接下来的图示来阐述一下:
一个类的class对象
里面的superclass指针
指向该类的父类的class对象
当Student
的instance对象
要调用Person
的对象方法时,会先经过isa
找到Student
的class对象
,而后经过这个class对象
的superclass
找到Person(Student的父类)
的class对象
,最后找到相应的对象方法(-方法)的实现进行调用
一个类的meta-class里面的superclass指针指向该类的父类的meta-class对象 当Student
的class对象
要调用Person
的类方法时,会先经过isa
找到Student
的meta-class对象
,而后经过这个meta-class对象
的superclass
找到Person(Student的父类)
的meta-class对象
,最后找到相应的类方法(+方法)的实现进行调用
上图来自苹果官方,完整描述了isa、superclass指针的做用,为了更加便于理解,咱们在后面的图例中用Student代替subclass,Person代替superclass,NSObject代替rootclass。
instance
的isa
指向class
class
的isa
指向meta-class
meta-class
的isa
指向基类的meta-class
class
的superclass
指向父类的class
,若是没有父类,superclass
指针为nil
meta-class
的superclass
指向父类的meta-class
,基类的meta-class
的superclass
指向基类的class
instance调用对象方法的轨迹 咱们以
[student abc];
为例,student
是Student
类的实例对象,调用轨迹以下图
对于
student
来讲,并不知道abc
方法在哪里,惟一知道的就是能够去它的class对象
里面找,
- 因而先经过
isa
指针进入Student
类的class对象
,若是在其中找到了abc
就直接进行调用,调用过程结束,- 没找到的话,就经过
class对象
的superclass
指针进入Student
类的父类,也就是Person
类的class对象
,重复上一步的查找逻辑- 以此类推,一层一层往上寻找,若是最终到了基类,也就是
NSObject
类的class对象
里面,还没找到的话,因为它的superclass
为nil
,最终就会碰到一个经典的报错[ERROR: unrecognized selector sent to instance]
,调用轨迹结束
class调用类方法的轨迹 咱们以
[Student abc];
为例调用轨迹图以下
对与Student类
来讲,abc
在哪也是不知道的,咱们知道类方法被规定放在meta-class对象
里面,因此
- 首先,经过
Student
的class对象
的isa指针
找到其meta-class对象
,而后在方法列表里面寻找是否有abc
,有的话就调用,调用逻辑结束。- 没有的话,就经过
meta-class对象
的superclass指针
找到Student
的父类Person
的meta-class对象
,而后查找abc
方法,找到就调用,结束调用轨迹- 没有的话,就经过
Person
的meta-class对象
的superclass指针
,重复上一步的流程- 一次类推,经过
meta-class对象
的superclass指针
,一层层往上查找- 若是到了基类(
NSObject
)的meta-class
还没可以找到abc
,此时比较特殊,接下来的superclass指针
会找到NSObjec
t的class对象
,你可能会奇怪,咱们调用一个类方法,怎么跑到class对象
里面来了,先保留你的疑问,只需记住,苹果确实是这么设计的,此时会继续在NSObjec
t的class对象
里面,寻找abc
,若是真的找到了abc
,就会调用- 若是尚未找到,因为此时的
superclass
是nil
,最终系统将给出报错
根据咱们上面的梳理和总结,咱们能够得出结论
isa(of instance) --> isa(of class) --> isa(of meta-class)
下面咱们经过代码来验证一下
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface CLPerson : NSObject <NSCopying>
@end
@implementation CLPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
Class personClass = [CLPerson class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
复制代码
咱们在代码中加入断点,经过控制台查看一下person
的isa
信息。可是貌似系统只给出了有限信息 还有个办法,能够右击红框中的
isa
,下拉菜单第一个有个打印功能Print Description of "xxx"
,能够获得更为详细的输出 看起来结果仍然被系统包裹了一层 若是你习惯直接在代码上快捷操做,也能够这么作试试
但我仍是喜欢用LLDB来查看,便于比较,和复制数据。
经过
p/x
命令来打印指针,/
后面是打印参数,x
参数表示用16进制数输出。由于咱们知道person
这个instance
的结构体的包含一个isa
成员变量,person
自己就是指针,因此能够经过person->isa
访问isa
的值。 代码里面,personClass
是Person类
的class对象
,输出结果显示, person的isa = 0x001d8001000014d1
personClass = 0x00000001000014d0
它俩。。。并不相等!!! 这是什么状况?不是说好了instance对象
的isa
指向class
对象嘛?
其实在64位机器出现以前,instance对象
的isa
确实是直接指向class对象
的, 也就是
person->isa == personClass
, 从64bit开始,isa
须要进行一次为运算,才能计算出真实的class对象
地址,系统给咱们提供了一个ISA_MASK,这个能够在objc4源码里面找到。我先直接贴出来
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
复制代码
你们请看清这里是分了arm64和x86_64的,分别对应的是移动设备开发和mac开发。个人代码是一个mac命令行工程,因此咱们用x86的这个值来试一下 能够看到,结果就显而易见了。经过和ISA_MASK进行一次&运算,咱们获得了personClass的地址。一样,咱们来试一下personClass的isa指针。
结果我试图经过
personClass->isa
先打印出其isa
指针的时候,获得了错误提示,告诉咱们说personClass
的类型Class
不是一个结构体,看不太明白,那就先查看一下Class
的定义,typedef struct objc_class *Class;
,而后在往下看一下objc_class
的细节
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
虽然这个结构体里面有isa指针,可是尾部的OBJC2_UNAVAILABLE;
提示咱们,这已是过期的API了。 不过咱们在第一篇文章中,已经得出结论,知道class对象里面第一个成员变量确实是一个isa指针,咱们能够经过一个小技巧来处理这个问题
struct cl_objc_class {
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
Class personClass = [CLPerson class];
struct cl_objc_class *personClass2 = (__bridge struct cl_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
}
return 0;
}
复制代码
咱们自定义一个struct,包含一个isa指针,而后再借助这个结构体类型来读取personClass里面的内容,如上代码,咱们用personClass2在来尝试一次 ok,结果显示,
class对象
的isa指针
通过ISA_MASK
转换以后,获得了正确的mete-class对象
的地址。到此,上面的面试题相信你们已经能够完整回答了。在用一个图来总结一下就是
你会许还会问,那么superclass指针呢,是否是也须要一个什么mask转换?答案是不须要的,能够用上面相同的方法进行验证,这里不做赘述。总之isa指针稍微特殊一点点,特别记住一下关于ISA_MASK的细节就行。
struct objc_class
在OC对象的本质(一)中,咱们得知了一个事实,在class对象中,存放了一个类的方法列表、属性信息、协议信息、成员变量信息;在meta-class对象中,存放了类的类信息。可是尚未对其仔细验证过。下面咱们就来研究一下这个问题。 由于class
和meta-class
的类型都是struct objc_class*
,因此咱们问题的答案,就都在这个objc_class
里面。上面的段落咱们已经看了它的结构了,很惋惜是一个已经废弃的API,因此咱们必须去最新的源码里面,去看一下它的实现。在objc4源码里面,咱们找到以下objc_class
的实现
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//下面是一大堆的方法
...
...
...
}
复制代码
这里的objc_class
是一个C++的结构体,若是对C++不太熟的话,先不用过度纠结,能够借用OC的类来理解就好了,它们的类似度很高,能够有成员变脸,也能够有方法,区别主要是一些成员变量的默认做用域不同。能够看到objc_class
继承自objc_object
,咱们能够在源码objc-private.h
里看一下objc_object
的实现
struct objc_object {
private:
isa_t isa;
//剩下的都是方法
...
...
...
}
复制代码
看得出来,其实就是一个isa指针。因而和objc_class
的内容融合一下,咱们能够理解成下面的这个结构
struct objc_class {
isa_t isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//下面是一大堆的方法
...
...
...
}
复制代码
很明显,objc_class
的内部,头两个成员分别是isa和superclass,跑不了。可是下面的好像不是咱们期待的内容,没看到方法列表、属性协议信息啥的呀。可是我门能够看到这里的第一个方法,返回一个class_rw_t *
,看字面意思,class表明类,rw一般表明读写(readwrite),t一般指的是 表信息(table),也就是类的可读写信息。那么咱们有理由怀疑这里面确定有宝贝。进去看一看 果真,原来方法、属性、协议信息都放在了这里。同时,咱们还发现了一个
class_ro_t *ro
,字面就是只读表,类对象里面有什么信息是只读的呢?没错,成员变量信息,因而咱们在进去验证一下 看上去推断是对的,确实找到成员变量信息。注意一下,这里的ivars是成员变量的描述信息,如名称,类型等,只须要一份的,因此存在class对象里面,成员变量的具体值是存在具体的instance对象里面的,不要理解混了。
对于meta-class来讲,结构上和class是同样的,只不过有些内容可能用不到,例如属性,协议列表。meta-class的类方法信息其实就放在咱们刚才看到的那个方法列表里面,没错。class对象的对象方法信息也正是放在这个方法列表里的
还有一点就是,怎么说呢,仍是看图明白
途中咱们看出来,
objc_class
有个成员变量bits
,正是经过 bits & FAST_DATA_MASK
,将objc_class
和它的可读写表关联起来了。下面我引用大神的一张ppt总结一下struct objc_class
的结构
面试题解答
- 对象的isa指针指向哪里?
instance
对象的isa
指针指向class
对象class
对象的isa
指针指向meta-class
对象meta-class
对象的isa
指针指向基类(也就是NSObject)的meta-class
对象
- OC的类信息存放在哪里?
- 对象方法,属性信息,成员变量信息,协议信息,存放在
class
对象中- 类方法,存放在
meta-class
对象中- 成员变量的具体值,存放在
instance
对象中
本系列文章总结自MJ老师在腾讯课堂开设的OC底层原理课程,相关图片素材均取自课程中的课件。