类的内存结构优化

WWDC2020对runtime的优化

类的数据结构

《类的探究分析》一文中就详细地解读了类的结构。在APP编译成二进制文件中,类的数据结构发生了变化,其中包含了最常被访问的信息,指向元类、父类和方法缓存的指针。以下是类的数据结构图:

类的数据结构

Clean Memory 和 Dirty Memory的区别

Clean Memory

Clean Memory指的是在程序运行中不会发生改变的内存class_ro_t就是属于Clean Memory的,class_ro_t`内存图解:

class_ro_t数据结构

  • clean memory 加载后不会发生改变的内存
  • class_ro_t 就属于clean memory,因为它是只读的,不会对齐内存进行修改
  • clean memory 是可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory,系统可以从磁盘中重新加载

Dirty Memory

Dirty Memory指的是在程序运行中会发生改变的内存,也就是我们俗称的脏数据。类的结构一经使用就会变成Dirty Memory,因为运行时会向它写入新的数据。例如往类中添加方法又或者加载类的子类父类,这里指的是class_rw_t。class_rw_t内存图解如下:

class_rw_t数据结构

  • Dirty Memory是这个类被分成两部分的原因,可以保持类加载后不会发生更改的数据越多越好,通过分离永远不会更改的数据,可以把大量的类数据存储为Clean Memory
  • class_rw_t(读写):类在加载的时候,属性(properties)协议(prococols)方法(methods)会被运行时动态的添加,也可以动态的修改(Method Swizzling)。所以类需要保存在class_rw_t中。
  • First Subclass、Next Subling Class:包含了运行时才会生成的信息First Subclass、Next Subling Class,所有的类都会变成一个树状结构,就是通过First SubclassNext Subling Class指针实现的,它允许运行时遍历当前使用的所有类
  • Demangled Name:这个字段使用的频率是比较少的,swift中才会使用。

总结:

dirty memory要比clean memory更有价值而且要多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory,这样才能不断的提高程序的性能。

class_rw_t 的优化

dirty memory在类第一次加载的时候就一直存在,runtime会为它分配额外的内存。运行时分配的存储容量时class_rw_t用于读取-编写数据,但是dirty memory中仍然存在着比较多的clean memory,为了提高空间的利用率,拆分出更多的clean memory,减少dirty memory容量是比奴可少的。
第一步:拆分出class_ro_t,即运行时不被修改的内存。如下图:

提取lass_ro_t

注意:由上图发现一个疑点,为什么方法,属性在class_ro_t中时,class_rw_t还要有方法,属性呢?

  • 属性和方法在运行时中有可能会发生更改,这需要放在class_rw_t中。
  • 在类加载的时候,可以往类中添加属性和方法。
  • class_ro_t只是可读的,需要放在class_rw_t中跟踪类的相关信息。
    第二步:拆分class_rw_t,提取其中的clean memory
    在读取-编写属性和方法的时候,只有10%的类都需要修改或者添加的,那么90%类可以说是不被修改的,那么就可以对class_rw_t进行拆分,拆分如下:
    class_rw_t拆分

    这样的话class_rw_t的大小就会减少一半,对于真的用到了被拆分出去的数据的时候,可以使用扩展(extension)来完成这些,添加到类中供其使用(大约90%的类不需要这个扩展)如下图:
    优化后的结构

总结

  • 当有类使用了category的时候,那么此时的类就有了class_rw_t的结构,如果未使用分类,那么类就是一个单纯的class_ro_t的结构。
  • 类结构的优化其实最要是分离出class_ro_tclass_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。

成员变量/实例变量和属性的区别

《类的探究分析》一文中提到了成员变量存放在class_ ro_t中,那么我们用过查找objc4源码得出下图:

实例变量存放位置

代码层面探究:

@interface XXPerson : NSObject
{
    int hobby;                 //成员变量
    NSObject *objc;            //实例变量
}
@property (nonatomic,strong) NSString *name;                 //属性
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic,assign) int age;
@end

通过xrun编译成main.cpp文件,查看底层代码:

  • xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (手机)
    main.cpp分析

    结论:
  • 属性在编译过程中自动加上settergetter方法。
  • 属性在底层编译阶段会变成_方式的成员变量。

补充

官方类型编码

官方类型编码

注意:

  • 编码文档存放在Apple Documents地址
  • Objective-C 不支持long double类型,@encode(long double)返回d,和double类型的编码值一样。
    案例分析:
    案例分析

    setName(v24@0:8@16)分析结果:
  • vvoid,代表无返回值。
  • 24setName函数的占用字节数。
  • @:参数,id或者self
  • 0:从0号位置开始。
  • :SEL
  • 8:从8号位置开始。
  • @:参数,setName
  • 16:从16号位置开始。

objc_setProperty与copy的关系

objc_setProperty方法相当于一个中间层方法,主要是避免了每个类都调用底层的objc_setProperty方法。当用copy关键字修饰属性时,该属性在编译时候setter方法就会从定向到objc_setProperty方法,不像其他属性·setter·方法使用首地址+内存偏移的方式找到方法实现。
示例代码:

@interface XJPerson : NSObject
@property (nonatomic,copy) NSString *name;         //注意每个属性的关键字
@property (nonatomic,strong) NSString *nickName;
@property (atomic,copy) NSString *address;
@property (atomic) NSString *school;
@end

编译之后查看main.cpp,得到下图:

main.cpp源码结果

结论:

  • 使用copy关键字修饰的属性底层setter方法重定向到objc_setProperty方法
  • 没使用copy关键字修饰的属性底层setter方法通过首地址+内存偏移来寻找并实现。

LLVM验证对象属性为copy时,setter方法的访问

验证流程图:

LLVM验证流程图

LLVM源码流程:objc_setProperty -> getSetPropertyFn -> GetPropertySetFunction -> PropertyImplStrategy -> IsCopy(判断copy关键字)
结论:无论属性是否是原子性还是非原子性的,用到copy关键字修饰的属性setter方法底层都用objc_setProperty实现,strong关键字无法通过最后得判断,需要通过首地址+内存偏移的方式实现。

总结:

底层代码的分析需要很好的耐心,过程也是非常的枯燥。但是当你弄明白了原理之后,就会发现知识是环环相扣的。非常高兴自己又多了一分收获,加油!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容