本章内容:
类的本质结构,以及类与元类的关系。类的属性、成员变量、实例方法、类方法等分别在什么地方存储。
总结:
1.类和元类的内存在编译后是已经分配好,而且只有一份;
2.类的isa -> 元类isa -> 根元类,而根元类的isa又指向它自己。根元类继承与它本身的类。例如NSObject和NSProxy的元类都是继承于它本身。而根类继承于nil;
3.类和元类的本质是objc_class结构体,而它的成员变量从上至下依次是:isa(8)、superClass(8)、chache(cache_t结构体16字节)、bits(class_data_bits_t结构体 8字节);
4.类的成员变量、属性、实例方法等可以通过bits进行查找。而类方法则在元类中存储也可以通过bits查找,类的属性、实例方法可以通过bits查找到class_rw_t(rw可读可写的,代表的是运行时允许改变的结构,dirty memory)结构体,然后通过rw的方法properties()、methods()、protocols()中找到,或者通过rw找到class_ro_t(ro只读的,代表编译时就已经确定的结构运行时不能修改,至于划分这两个是WWDC2020runtime进行的优化 clean memory)找到其成员baseProtocols、ivars、basePropertyies、函数baseMethods()等。类的成员变量在ro中找到
5.看下图,得到:对象的isa -> 类,类的isa -> 元类, 元类的isa -> 根元类,根元类的isa -> 根元类本身;然后再得到类的继承关系:SubClass -> SuperClass -> RootClass -> nil;然后还可以得到元类的继承关系:SubClass(meta) -> SuperClass(meta) -> RootClass(meta) -> RootClass -> nil
探索类的本质结构以及类与元类的关系
探索类的本质结构,我们要先举例说明,如下图类Person 以及 它的子类 Manger。探索Person *p1 = [[Person alloc] init]
先告诉结果其Person类的内容,接下来会看objc源码进行验证:1.类的isa,2.superClass父类指针地址 3.cache,4. bits。
而且我们要明白一点
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
NSLog(@"----%@-----%p", p1, &p1);
NSLog(@"----%@-----%p", p2, &p2);
输出结果:
----<Person: 0x101a06450>-----0x7ffeefbff408
----<Person: 0x101a064b0>-----0x7ffeefbff410
&p1(0x7ffeefbff408,指针的指针) -> p1(0x101a06450),p1的isa -> 类。
根据objc 查看类的内存结构
我们通过clang转c++底层代码的时候已经知道对象的本质是objc_object结构体。但是我们发现对象的isa是指向其类的地址的而且还发现类的地址竟然也会有结果,而且在我们OC层面的时候那个Class也代表的是类,而Class其实objc_class *结构体指针,我们是不是就可以知道类的本质就是objc_class结构体,而且它是继承于objc_object结构体的。
探索类与元类之间的关系
1.继续用LLDB进行验证,然后发现了Person类的isa所指向的内存地址输出显示还是一个Person。是不是就验证了我们之前的猜想。
2.继续进行查看地址Person元类地址0x0000000100008680的内容,发现了其isa指向了NSObject元类0x000000010036a0f0
3.然后再探索NSObject元类0x000000010036a0f0
总结:得到上面总结2、3、5
副总结:本章没有进行说明,但是可以自己去验证 类继承:Manger -> Person -> NSObject -> nil; 元类继承:Manger -> Person -> NSObject -> (根元类本身)
探索类的属性、成员变量、实例方法、类方法存储地方
探索类例子:获取Person类中的属性、成员变量、实例方法
验证:本内容进行简略,如有需要可以详细看objc源码内容只说大概流程以及验证方法
1.获得Person类
获取其bits。bits的指针位置是Person的isa + 0x20(32字节)。至于为什么请查看结构体objc_class。它的成员bits在最后一个所以是:类地址+ isa的大小(8字节)+superclass大小(8字节)+cache大小(16字节)
2.获取class_rw_t结构体
objc_class内部方法class_rw_t *data()获取class_rw_t结构体
3.获取属性
调用class_rw_t方法const property_array_t properties() const
4.获取实例方法
调用class_rw_t方法const method_array_t methods()
5.获取成员变量
调用class_rw_t方法const class_ro_t *ro() const获取结构体class_ro_t,然后再打印其成员ivars
6.获取类方法
获取类方法和4是一致的只是需要用元类去获取。
7.获取protocol协议
因为是后续补充的,所以跟上面不一样,只演示实例方法,先上例子:
验证:
注意:由于它底层给的是一个protocol_ref_t,其实它就是protocol_t *结构体指针地址。所以我们要进行强转
1.先看protocol_list_t结构体,我们要先获取的是list[0]
2.LLDB进行强转
3.查看protocol_t输出。
- mangledName:代表的是本协议地址
- protocols:代表的是继承协议的地址(已经验证为NSObject协议,而NSObject协议继承于nil。无中生有)
- instanceMethods:协议的实例方法
- classMethods:协议的类方法
- optionalInstanceMethods:协议的可选实例方法
- optionalClassMethods:协议的可选类方法
- instanceProperties:实例属性
- _classProperties:类属性(这个不确定,不知道怎么验证)
补充
苹果为什么要将类的结构分为rw,ro,rwe呢?
因为运行时的机制,我们可能在运行时对类插入方法,属性等,这就造成了很大一部分内存浪费。所以WWDC2020就公布了这一结果,由过去的全部堆放在一块,分为了rw(class_rw_t), ro(class_ro_t), rwe(class_rw_ext_t)。苹果尊崇dirty越少,clean越多越好。因为在底层虚拟内存机制(当内存不足时会把一部分内存回收掉,后面再次使用的时候从硬盘加载出来,swap机制),clean Memory是可以从硬盘中重新加载,但是dirty Memory是运行时的数据,这部分数据不能被轻易回收,一旦回收就意味着么的了。所以一般会回收clean Memory,但是如果说dirty Memory过大那也不惯他。
rw:可读可写(dirty Memory),用户可以在运行时插入方法属性到这里。运行时内存会改变。但是rw包含了ro和rwe
ro: 只读(clean Memory),当类加载后,其成员变量,属性,方法在这里,运行时的修改不影响这里
rwe: 可扩展,意思是类的额外信息,但是实际使用时候,只有很少的类会改变其内容,避免消耗就有了rwe。