内容简介:在Runtime源码中,我们能发现NSObject对象只有一个Class对象其实是一个指向objc_class结构体的指针。
2.1 Class类型
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
复制代码
在Runtime源码中,我们能发现NSObject对象只有一个 Class 类型的成员变量: isa
typedef struct objc_class *Class; 复制代码
Class对象其实是一个指向objc_class结构体的指针。
struct objc_object {
private:
isa_t isa;
// 这里省略成员变量以及方法...
}
复制代码
Class 类型本质是个结构体,该结构体中存储了该 NSObject 中的所有信息。
那么一个NSObject对象占用多少内存?
NSObjcet实际上是只有一个名为isa的指针的结构体,因此占用一个指针变量所占用的内存空间大小,如果64bit(64位架构中)占用8个字节,如果32bit占用4个字节。
2.2 Class方法
- (Class)class {
return object_getClass(self);
}
复制代码
在Runtime源码中,我们调用Class方法,其实是在调用 object_getClass(self) ,最终通过下面代码获取结果值。
inline Class
objc_object::ISA()
{
// 忽略其它方法
return (Class)(isa.bits & ISA_MASK);
}
复制代码
2.3 isa.bits & ISA_MASK
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
};
复制代码
上述源码可以知道, isa_t 是个联合体。
typedef unsigned long uintptr_t; 复制代码
bits 是 long 类型的数值。
在 isa.h 中,可以找到 ISA_MASK 源码
# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # else # error unknown architecture for packed isa # endif 复制代码
可知,其实 ISA_MASK 还是个数值类型
我们可以看到class方法最终获取的即是:
结构体 objc_object 的 isa.bits & ISA_MASK 的数值计算结果。
3.NSObject对象的isa_t
3.1 isa_t
// 精简过的isa_t共用体
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
#endif
};
复制代码
上述源码中 isa_t 是union(共用体)类型。可以看到共用体中有一个结构体,结构体内部分别定义了一些变量,变量后面的值代表的是该变量占用多少个二进制位,也就是位域技术。
源码中通过共用体的形式存储了64位的值,这些值在结构体中被展示出来,通过对 bits 进行位运算而取出相应位置的值。
3.2 共用体
在进行某些算法的 C语言 编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。
优点:可以很大程度上节省内存空间。
union U1
{
int n;
char s[11];
double d;
};
复制代码
对于U1共用体,s占11字节,n占4字节,d占8字节,因此其至少需11字节的空间。然而其实际大小并不是11,用运算符sizeof测试其大小为16。这是因为 内存对齐原则 ,11既不能被4整除,也不能被8整除。因此补充字节到16,这样就符合所有成员的自身对齐了。所以 联合体的内存除了取最大成员内存外,还要保证是所有成员类型size的最小公倍数
对比类的内存对齐:
原则 1. 前面的地址必须是后面的地址正数倍,不是就补齐。 原则 2. 整个Struct的地址必须是最大字节的整数倍。
@interface MXRPerson : NSObject{
int _age;
}
复制代码
person对象的第一个地址要存放isa指针需要8个字节,第二个地址要存放_age成员变量需要4个字节,因此person对象就占用16个字节空间。
代码验证:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 验证内存地址
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
NSLog(@"%zd",class_getInstanceSize([MXRPerson class]));
}
return 0;
}
// 8 16
复制代码
3.3 isa中存储的信息及作用
struct {
// 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
// 1代表优化后的使用位域存储更多的信息。
uintptr_t nonpointer : 1;
// 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_assoc : 1;
// 是否有C++析构函数,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1;
// 存储着Class、Meta-Class对象的内存地址信息
uintptr_t shiftcls : 33;
// 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
// 是否有被弱引用指向过。
uintptr_t weakly_referenced : 1;
// 对象是否正在释放
uintptr_t deallocating : 1;
// 引用计数器是否过大无法存储在isa中
// 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1;
// 里面存储的值是引用计数器减1
uintptr_t extra_rc : 19;
};
复制代码
此时我们重新来看 ISA_MASK 的值 0000000ffffffff8 转为二进制:
可以看出 ISA_MASK 的值转化为二进制中有33位都为1,所以按位与的作用是可以取出这33位中的值。我们再回头看看 isa_t 的源码,不难发现,这33位对应的是结构体的 shiftcls 的位域。那么 ISA_MASK 同 shiftcls 进行按位与运算即可以取出Class或Meta-Class的值(内存地址的值)。
同时可以看出 ISA_MASK 最后三位的值为0,那么任何数同 ISA_MASK 按位与运算之后,得到的最后三位必定都为0,因此任何类对象或元类对象的内存地址最后三位必定为0,转化为十六进制末位必定为8或者0。
对象的isa指针需要同 ISA_MASK 经过一次&(按位与)运算才能得出真正的Class对象地址。
代码验证
- (void)viewDidLoad {
[super viewDidLoad];
MXRPerson *person = [[MXRPerson alloc]init];
NSLog(@"%p",[person class]);
NSLog(@"%@",person);
}
复制代码
2019-04-24 18:21:30.424630+0800 IsaTestDemo[58799:8221193] 0x1005c8db0 (lldb) p/x person->isa (Class) $0 = 0x000001a1005c8db1 MXRPerson 复制代码
shiftcls 中存储类对象地址。把转为2进制的实例对象isa地址与转为2进制的类对象地址作对比,可以看出存储类对象地址的33位二进制内容完全相同。
4.Class对象在内存中存储的信息
4.1 instance对象在内存中存储的信息包括
- isa指针
- 其他成员变量
4.2 class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property),类的成员变量信息(ivar)
- 类的对象方法信息(instance method),类的协议信息(protocol)
4.3 class_rw_t & class_ro_t
我们发现class_rw_t中存储着方法列表,属性列表,协议列表等内容。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法进行缓存
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
复制代码
class_rw_t 中的methods是二维数组的结构,并且可读可写,因此可以动态的添加方法,并且更加便于分类方法的添加。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
复制代码
而class_rw_t是通过bits调用data方法得来的,我们来到data方法内部实现。我们可以看到,data函数内部仅仅对bits进行&FAST_DATA_MASK操作
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
复制代码
成员变量信息则是存储在class_ro_t内部中的,我们来到class_ro_t内查看。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //实例对象大小
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成员变量
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
复制代码
4.4 每个类在内存中有且只有一个meta-class对象,在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法的信息(class method)
5.验证对象的isa指针指向
1.当对象调用实例方法的时候,我们上面讲到,实例方法信息是存储在class类对象中的,那么要想找到实例方法,就必须找到class类对象,那么此时isa的作用就来了
instance的isa指向class,当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用。
2.当类对象调用类方法的时候,同上,类方法是存储在meta-class元类对象中的。那么要找到类方法,就需要找到meta-class元类对象,而class类对象的isa指针就指向元类对象
class的isa指向meta-class当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
3.当对象调用其父类对象方法的时候,又是怎么找到父类对象方法的呢?,此时就需要使用到class类对象superclass指针。
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用,同样如果Person发现自己没有响应的对象方法,又会通过Person的superclass指针找到NSObject的class对象,去寻找响应的方法
4.当类对象调用父类的类方法时,就需要先通过isa指针找到meta-class,然后通过superclass去寻找响应的方法
当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用
代码验证:
struct mxr_objc_class{
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 如何证明isa指针的指向真的如上面所说?
NSObject *object = [[NSObject alloc] init];
Class objectClass = [NSObject class];
// 我们自己创建一个同样的结构体并通过强制转化拿到isa指针。
struct mxr_objc_class *objectClass2 = (__bridge struct mxr_objc_class *)(objectClass);
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"%p %p %p", object, objectClass, objectMetaClass);
}
return 0;
}
复制代码
验证结果1
(lldb) p/x object->isa (Class) $0 = 0x001d800100b16141 NSObject (lldb) p/x objectClass (Class) $1 = 0x0000000100b16140 NSObject (lldb) p/x 0x00007ffffffffff8 & 0x001d800100b16141 (long) $2 = 0x0000000100b16140 复制代码
object-isa指针地址0x001dffff96537141经过同0x00007ffffffffff8位运算,得出objectClass的地址0x00007fff96537140
验证结果2
我们来验证class对象的isa指针是否同样需要位运算计算出 meta-class 对象的地址。 以同样的方式打印 objectClass->isa 指针时,发现无法打印。
(lldb) p/x objectClass->isa error: member reference base type 'Class' is not a structure or union 复制代码
为了拿到isa指针的地址,我们自己创建一个同样的结构体并通过强制转化拿到isa指针。
(lldb) p/x objectClass2->isa (Class) $0 = 0x001d800100b160f1 (lldb) p/x objectMetaClass (Class) $1 = 0x0000000100b160f0 (lldb) p/x 0x00007ffffffffff8 & 0x001d800100b160f1 (long) $2 = 0x0000000100b160f0 复制代码
objectClass2的isa指针经过位运算之后的地址是meta-class的地址。
以上所述就是小编给大家介绍的《Runtime源码浅析(内部分享)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro CSS and HTML Design Patterns
Michael Bowers / Apress / April 23, 2007 / $44.99
Design patterns have been used with great success in software programming. They improve productivity, creativity, and efficiency in web design and development, and they reduce code bloat and complexit......一起来看看 《Pro CSS and HTML Design Patterns》 这本书的介绍吧!