iOS底层原理08:类结构分析——bits属性

iOS底层原理07:类 & 类结构分析中我们对类结构有了大概的认识,本文主要探索objc_classbits属性,探索成员变量属性方法(对象方法、类方法)、协议等是如何存储的

【WWDC2020】类数据结构的优化

WWDC2020中关于数据结构的变化(Class data structures changes)视频地址
Object-C运行时会使用这些数据结构来跟踪类,👇下面我们先来了解 Clean MemoryDirty Memory的区别,方便我们更好得理解类数据结构的优化

Clean Memory 和 Dirty Memory的区别

Clean Memory

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

Dirty Memory

  • dirty memory是指在进程运行时会发生更改的内存
  • 类结构一经使用就会变成dirty memory,因为运行时会向它写入新的数据。(例如:创建一个新的方法缓存,并从类中指向它)
  • dirty memoryclean memory要昂贵得多,只要进程在运行,它就必须一直存在
  • macOS可以选择换出dirty memory,但因为iOS不使用swap,所以dirty memory在iOS中代价很大

因此,苹果为了性能优化,类数据被分成两部分,可以保持清洁的数据越多越好,通过分离出那些永远不会更改的数据(即class_ro_t),可以把大部分的类数据存储为clean memory

类结构的优化

虽然class_ro_t这些数据足够我们使用类,但因为OC的动态特性,运行时需要跟踪每个类的更多信息,所以当一个类首次被使用runtime会为它分配额外的存储空间

  • 这个运行时分配的存储容量是class_rw_t,用于读取-编写数据,在这个数据结构中,我们存储了只有在运行时才会生成的新消息

优化之前,类结构如下👇


image.png

所有的都会链接成一个树状结构,通过使用First SubclassNext Sibling Class指针实现的,这允许运行时遍历当前使用的所有类。

【问题】 为什么class_rw_tclass_ro_t中都存在方法、属性呢?

  • class_rw_t可以在运行时进行更改
  • category被加载时,它可以向类中添加新的方法
  • 我们还可以使用运行时 API动态添加方法,如method_setImplementation
  • class_ro_t 是只读的,所以我们需要在class_rw_t中来跟踪这些东西

在任何给定的设备中,都有许多类在使用,苹果开发人员在iPhone上的整个系统中测量了,class_rw_t结构占用了相当多的内存,【切记】我们在读取-编写部分需要这些东西,因为他们可以在运行时更改。
【问题】如果缩小class_rw_t的结构呢?

  • 苹果开发人员发现,大约只有10%的类真正地更改了他们的方法
  • 而且只有Swift类会使用这个demangled name字段(只有访问它们Objective-C名称时才需要

所以我们可以拆掉那些平时不用的部分,以达到内存优化,如下图所示:

image.png

  • 这样class_rw_t的大小会减少一半
  • 对于那些确实需要额外信息,才会有分配这些扩展记录,即多了一层class_rw_ext_t数据,而剩下90%的类,从来不需要这些扩展数据,也就没有class_rw_ext_t这层数据结构

我们可以通过heap来检查正在运行的进程所使用的堆内存

//查看微信进程,在活动监视器中查看,微信进程=591
heap 591 | egrep "class_rw|COUNT|class_ro"
image.png

总结

class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。现在大家对类应该有个更清楚的认识。

lldb调试分析

准备工作

定义两个类

  • 继承自NSObject的类HTPerson
@interface HTPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayBye;
@end

@implementation HTPerson
- (void)sayHello {
    NSLog(@"%@",__func__);
}
+ (void)sayBye {
    NSLog(@"%@",__func__);
}
@end
  • 继承自HTPerson的类HTTeacher
@interface HTTeacher : HTPerson
@end

@implementation HTTeacher
@end
  • main.m中代码如下
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HTPerson *p1 = [HTPerson alloc];
        HTPerson *p2 = [[HTPerson alloc] init];
        HTTeacher *t = [[HTTeacher alloc] init];
        NSLog(@"end--%@--%@", @"123", p1);
    }
    return 0;
}

lldb调试class_rw_t结构

断点调试步骤如下

  • 获取类的首地址:p/x HTPerson.class

  • 获取bits的地址:类首地址 + 0x20p/x (0x00000001000082f8 + 0x20)

  • 通过bits->data()获取class_rw_t结构体地址

  • 打印class_rw_t结构体数据

  • 【1】执行完HTPerson *p1 = [HTPerson alloc];,查看class_rw_t数据,发现witness的值为0firstSubclass的值为nil

image.png
  • 【2】执行完HTPerson *p2 = [[HTPerson alloc] init];,查看class_rw_t数据,发现witness的值变为1
image.png
  • 【3】执行完HTTeacher *t = [[HTTeacher alloc] init];,即使用子类,发现firstSubclass的值变成了HTTeacher
image.png

属性探究

  • class_rw_t结构体提供了properties()函数来获取类的属性,得到property_array_t数据
image.png
  • 查看property_array_t数据结构,发现它继承自list_array_tt,并且有property_tproperty_list_t两层数据
image.png
  • 继续查看list_array_tt数据结构,发现内部有个联合体数据,其中list属性列表property_list_t的指针地址
image.png
  • 查看property_list_t数据结构,继承自entsize_list_tt,提供了泛型模版数据
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
  • 继续查看entsize_list_tt数据结构,终于找到了get()方法(获取对应index位置的属性)
image.png

👇通过lldb断点来查看HTPerson的属性

image.png

通过class_rw_t -> properties()获取的属性列表,只存储了两个属性:nameage
【问题】我们声明的变量-hobby保存在哪里呢?

成员变量探究

属性列表中没有存储变量,观察发现class_rw_t还有一个获取class_ro_t *的方法const class_ro_t *ro() const {},成员变量会不会在class_ro_t中,源码查看class_ro_t结构体定义

    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    // ...省略

class_ro_t是结构体类型,有一个const ivar_list_t * ivars;变量。从名字我们可以猜到里面应该存储变量。👇通过lldb验证如下图:

image.png

总结

  • 变量的底层实现是ivar_t,存储在class_ro_t中的变量列表ivars
  • 系统会给属性自动生成一个带_属性名变量,存储在class_ro_t的变量列表中

方法探究

实例方法探究

lldb调试如下👇


image.png
  • 类的对象方法列表通过bits->data()->methods()获取
  • 类的对象方法列表在底层结构是method_list_t
  • p $7.get(index)在方法列表中获取不到具体的值,因为method_t中进行了处理,需要通过big()获取方法
  • 类的方法列表中没有类方法

从上图打印结构可以看出,类会为属性提供默认的set、get方法,
但是我们没有发现HTPerson的类方法+ (void)sayBye;

类方法探究

对象的方法是存储在中,那么类方法可能存储在元类中。按照这个思路探究下

image.png

从打印结果可以得知:类方法存储在元类方法列表

协议探索

  • 新增一个协议HTPersonProtocol,让HTPerson遵守协议
@protocol HTPersonProtocol <NSObject>
- (void)protocolMethod1;
- (void)protocolMethod2;
@end

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

推荐阅读更多精彩内容