前言
上一篇文章中ios 底层原理 03–isa 的走位图&类的结构探索对类结构进行了整体大概流程的分析。今天我们对类结构进行些补充,以及过程中出现的疑问进行说明,所以知识点比较散
WWDC2020上关于类的优化
链接如下Advancements in the Objective-C runtime
先上结论:
Dirty Memory跟Clean Memory的区别
Clean Memory
clean memory加载后不会发生改变的内存class_ro_t就属于clean memory,因为它是只读的不会对其内存进行修改clean memory是可以进行移除的,从而节省更多的内存空间,因为如果你需要clean memory的时候系统可以从磁盘中重新加载
Dirty Memory
dirty memory是指在进程运行时会发生改变的内存- 类结构一经使用就会变成
dirty memory,因为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它,初始化类相关的子类和父类 - dirty memory是类数据被分成两部分的主要原因
Dirty Memory跟Clean Memory的区别
dirty memory要比clean memory昂贵的多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory,这是苹果追求的
class_rw_t 优化
当一个类第一次被创建跟使用时,runtime会为它分配额外的存储容量,运行时分配的存储容量就是class_rw_t。class_rw_t用于读-写数据,在这个数据结构中存储的是只有运行时才会生成的新数据
所有的类都是通过firstSubclass和nextSiblingClass指针实现的树结构,这样运行时会遍历当前使用的所有类
问题1:从上图可以看出 rw 跟 ro中都有 Methods方法Protocols协议Properties属性。这是为啥呢?
- 因为它们可以在
运行时进行更改 - 当
category被加载时,它可以向类中添加新的方法 - 通过
runtimeAPI手动向类中添加属性和方法 class_ro_t是只读的,所以我们需要在class_rw_t中来跟踪这些东西
问题 2:class_rw_t结构在系统中,占用很多的内存,怎么优化结构?
- 我们在
rw部分需要这些东西,是因为它们在运行时可以被修改,但是大约90%的类是不需要修改的而且只有在swift中才会使用这个demangledName字段,但是swift类很多时候也不需要这个字段,除了访问它们Objective-C名称时才需要。所以可以拆除那些我们平时不常用的部分放在class_rw_ext_t里面。

总结
class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从class_rw_ext_t(扩展记录)中分配一个,滑到类中供其使用
类方法是否在元类中
对象(实例)方法是存放在类中的,这时候类相对于元类来说就是类对象,在对象中的类方法==类对象的实例方法,类对象的实例方法应该是元类中
举例子说明
LGPerson *person的减号方法 [person sayhello] 是存放在LGPerson中的
这时候LGPerson相对于LGPerson 的元类(用 XXX代替)来说就是XXX对象,在对象*person中的 +加号方法 init==LGPerson的减号方法init,所以XXX 的对象的减号方法应该在XXX中
在自定义的类中添加方法名相同的实例方法和类方法,编译运行不会报错。如果都是放在类中,那么编译器肯定会出问题,因为识别不了哪个是我需要的方法,我们猜想类方法不在类中,那么可能在元类中。在上一篇文章中Meta Class(元类)中 我们说过原理,下面我们通过代码来验证一下。
class_getInstanceMethod得到类的实例方法
编写一段代码 其中
sayClassMethod为类方法sayInstanceMethod为对象方法

可以看出 类的实例方法中有sayInstanceMethod方法 元类的实例方法中有sayClassMethod方法
class_getClassMethod 得到类的类方法
![图片[3]-ios 底层原理 04– 深入探究类的结构-一一网](https://www.proyy.com/skycj/data/images/2021-07-01/79360ace1dd6eac759fb8c80c2c68586.jpg)
可以看出 类的类方法中有sayClassMethod方法 元类的类方法中有sayClassMethod方法
总结
实例方法在类中,类方法在元类中- 编译器自动生成
元类,目的是存放类方法
关于 2个 API的问题
sayInstanceMethod怎么在LGPerson类中怎么没有找到
![图片[4]-ios 底层原理 04– 深入探究类的结构-一一网](https://www.proyy.com/skycj/data/images/2021-07-01/38d54dbee5936f6eb7a04cd059a0595c.jpg)
源码分析:class_getClassMethod就是去元类中,查找实例方法。打印结果sayInstanceMethod不存在元类中,找不到。sayClassMethod在元类中,可以找到。在底层不存在类方法,都是按普通方法进行查找。
class_getMethodImplementation为啥会有指针地址 imp1和imp4应该是nil

IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
复制代码
源码分析:当imp = nil时,系统会返回_objc_msgForward,所以imp1和imp4才有函数指针地址,而且地址相同
成员变量,实例变量,属性的区别
成员变量
- 在
Objective-C中写在类声明的大括号中的变量称之为成员变量,例如int a,NSObject *obj - 成员变量用于
类内部,无需与外界接触的变量
@interface LGPerson : NSObject
{
NSObject *objc; //成员变量 实例变量
NSString *nickName;//成员变量
}
复制代码
实例变量
- 变量的数据类型不是
基本数据类型且是一个类则称这个变量为实例变量,例如NSObject *obj 成员变量包含实例变量
成员变量和属性的区别
- 成员变量:在底层没有其他操作只是变量的
声明 - 属性:系统会自动在底层添加了
_属性名变量,同时生成setter和getter方法
SEL和IMP关系
- SEL:方法编号 SEL 相当于书本的目录名称(第几页的大纲)
- IMP:函数指针地址 IMP 相当于书的页码(第几页地址)
setter方法的底层实现
在探究属性和成员变量的区别时,发现属性setter方法有的是通过objc_setProperty实现的,有的是直接内存偏移获取变量地址,然后赋值
通过探索 llvm 可以得出
/// 对象 sel 偏移量 值 原子性 是否copy
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
// 是否是 Copy
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
// 是否是 可变的Copy
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
复制代码
发现他实际调用了 reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
// 修改 ISA
object_setClass(self, newValue);
return;
}
id oldValue;
// 定位(首地址+偏移量)
id *slot = (id*) ((char*)self + offset);
// Copy相关操作
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;// 相同的话不做操作
// 引用计数+1
newValue = objc_retain(newValue);
}
// 原子性相关
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
// 加锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
// reloase 旧值 引用计数 -1
objc_release(oldValue);
}
复制代码
结论
copy修饰的属性使用objc_setProperty方式实现,其它属性使用内存偏移实现- 苹果没有把所有的setter方法全部写在底层,因为如果底层需要维护,修改起来特别麻烦。搞了个
适配器中间层,中间层的作用是供上层的setter调用,中间层对属性的修饰符进行判断走不同的流程,调用底层的方法实现
中间层的优点:底层变化上层不受影响,上层变化底层也不会受影响
getter方法的底层实现
在探究属性和成员变量的区别时,基本上通过内存偏移获取变量地址获取值。 只有少数条件下才通过objc_getProperty方式实现
通过探索 llvm 可以得出
结论
-
ARC:
atomic+ copy修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现 -
MRC:
atomic+ retain修饰的属性使用objc_getProperty方式实现,其它属性使用内存偏移实现





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)