2. OC对象底层探究 - 476139183/Learning-iOS GitHub Wiki
我们可以通过下面的命令 对指定文件进行代码转换。这样方便我们查看底层实现
//! 可以指定 arm64 架构, 如果需要链接其他框架,使用-framework参数。比如-framework UIKit
//! -o 输出的 cpp 文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc mian.m -o main-arm64.cpp万物皆对象,在iOS里面,对象又分3大种类:
- instance 对象(实例对象)
- class 对象(类对象)
- meta-class 对象(元类对象)
1.1 instance 对象
instance 对象就是通过 alloc 出来的对象,每次调用 alloc 都会产生新的 instance 对象,分别占据着不同的内存。
而 instance 对象在内存存储的信息包括
- isa 指针
- 其他成员变量
这个我们在分析 OC 对象 的内存分布规则 中 已经可以得知, 比如下面这个例子:
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];那么他们的内存分配如下:
这两个实例对象分别有自己的内存存储,存储着其他成员变量的值
1.2 Class 对象
我们通 class 方法或 runtime 的 object_getClass() 方法得到一个class对象。class对象也就是类对象。比如:
Class objectClass1 = [p1 class];
Class objectClass2 = [NSObject class];
// runtime
Class objectClass3 = object_getClass(p1);
Class objectClass4 = object_getClass(p2);每一个类在内存中有且只有一个class对象。属于全局的
- isa指针
- superclass指针
- 类的属性信息(@property),类的成员变量信息(ivar)
- 类的对象方法信息(instance method),类的协议信息(protocol)
成员变量的具体值是存储在实例对象中的,因为不同实例对象是不共享成员变量的值的。但是成员变量叫什么名字,是什么类型,只需要有一份就可以了。所以存储在class对象中。方法,协议等同理
1.3 meta-class 对象
同样,我们也可以通过 runtime 的 object_getClass 和 objc_getMetaClass 获取元类对象代码:
//! runtime 中传入类对象 此时得到的就是元类对象
Class objectMetaClass = object_getClass([NSObject class]);
//! 或者调用更底层的方法获取元类对象
Class objectMetaClass1 = objc_getMetaClass("NSObject");
//! 判断该对象是否为元类对象
class_isMetaClass(objectMetaClass) 每个类在内存中有且只有一个meta-class对象。 meta-class对象和class对象的内存结构是一样的,但是用途不一样。
元类对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法的信息(class method)
meta-class 对象和 class 对象的内存结构是一样的,所以 meta-class 中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。
OC对象的基础类 就是 NSObject,而 我们发现 NSObject 的 OC 定义如下:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}typedef struct objc_class *Class;实际上,通过继承的关系,我们可以得出结论,所有的对象,都有一个 isa 指针,而这个isa指针指向 objc_class, 那么 **objc_class**又是什么呢?
isa 其实就是
it is a object,所以我们可以认为,只要有 isa 指针,那么它就是一个OC对象
我们 通过 objc4源码 可以得知其结构:
objc_class 继承自 objc_object。 这个 objc_object 也是一个 isa 指针。如果说,一个实例对象的 isa 指向的是它的 类对象(class),那么这个 类对象的isa指针就指向它的元类(metaclass),也即类的类,就是元类。
他们的关系如下:
这样,我们在方法的寻址时,逻辑步骤可以保持一致:
- 当调用 实例方法的时候,我们可以通过 instance 的 isa 找到 class,然后在里面寻找 实例方法 进行调用。
- 当调用 类方法的时候,我们通过 class 的 isa 找到 meta-class,然后在里面寻找 类方法 进行调用。
我们发现 类对象 和 元类对象 都有一个 superclass的指针,指向的也是一个 Class 类型。假定我们在创建一个 Student 类 继承自 Person 类,那么不难得出。他们的 superclass指针的指向:
- 当Student的 instance对象要调用 Person 的对象方法时,会先通过 isa找到 Student的 class,然后通过 superclass找到 Person 的class,最后找到对象方法的实现进行调用
与之对应的,两个元类关系如下:
- 当Student的 class 要调用 Person 的类方法时,会先通过 isa找到 Student 的 meta-class,然后通过 superclass 找到 Person 的 meta-class,最后找到类方法的实现进行调用
很多关系看图应该很清晰了,那么有两点要总结一下:
-
元类的isa指向的是,根类的元类
-
根类的元类的isa指向自己,而它的super_class指向的是根类。
我们在调用 [p class] 的时候,可以看到如下函数调用:
获取class的对象的时候,并不是直接取 isa 指针,而是对其进行了一次与运算。
也可以直接去查看 源码
struct objc_object {中的ISA()函数
事实上从 64bit 开始,iOS 对 isa 做了掩码,需要进行一次位运算,才能计算出真实地址
MASK掩码:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif Person *person = [[Person alloc] init];
NSLog(@"personClass=%p",[Person class]);
NSLog(@"person=%p",person);我们分别得出 实例对象和类对象的地址。
personClass=0x600000000350
person=0x100002bd8我们先获取 person的isa指针的内存
(lldb) p/x person->isa
(Class) $0 = 0x001d800100002bd9 Person因为我是在macOS环境编译代码的,所以用的是 x86_64 架构。我们对其进行一次位运算
(lldb) p/x 0x001d800100002bd9 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100002bd8可以看到 进行了位运算之后,两次地址是一样的。同理可以推断 类对象 和 元类对象 也存在一样的位运算关系。
至此,我们算是完成了对 OC 对象的部分探究了








