iOS底层原理之类的原理分析二

1.WWDC20runtime对于类的数据结构的优化

引用作者Ben的原话:此次优化不需要改动任何代码,并且不需要学习新的API,运气好的话,什么都不需要做,你的app也会变得很快。是runtime关于内存的优化。

视频地址

1.1 类的运行时数据结构的变化

在磁盘上,APP二进制文件中类对象本身的数据结构中包含了最常被访问的信息:指向元类、父类和方法缓存的指针,同时还有一个指向更多数据的指针,存储额外信息的地方叫做class_ro_tRo代表只读,里面包括类名称、方法、协议和实例变量的信息等,Swift类和OC来共享这一基础结构。

image.png

当类第一次从磁盘加载到内存中时,它们也是这样的,但是一经使用,它们就会发生变化,在了解变化之前,需要先了解Clean MemoryDirty Memory的区别。

1.1.1 Clean Memory

Clean Memory是指加载后不会发生更改的内存。class_ro_t就属于Clean Memory,因为它是只读的。

1.1.2 Dirty Memory

Dirty Memory是指在进程运行时会发生更改的内存。当一个类首次被使用时,运行时会为它分配额外的存储容量class_rw_t,用于读写数据,class_rw_t就属于Dirty Memory

Dirty Memory是这个类被分成两部分的原因,可以保持类加载后不会发生更改的数据越多越好,通过分离永远不会更改的数据,可以把大量的类数据存储为Clean Memory

class_rw_t数据分析:

1. `First Subclass`、`Next Sibling Class`:运行时才会生成的新信息,通过使用`First Subclass`和`Next Sibling Class`指针将所有的类都链接成一个树状结构,这允许运行时遍历当前的所有类。
2. `Methods`、`Properties`、`Protocols`:当`category`被加载时,它可以向类中添加新的方法,而且程序员可以通过运行时API动态的添加他们。
3. `Demangled Name`:这个是只有Swift才会使用的字段,因为整个数据结构OC与Swift是共享的,但是Swift类本身并不需要这个字段,是为了有人要访问Swfit的OC名称的时候使用的,利用率比较低。
复制代码

特点:

  • 内存中Dirty MemoryClean Memory更多,只要进程在运行,它就一直存在。
  • Clean Memory可以从内存中移除,需要时再从磁盘中加载

image.png

1.1.3 Dirty Memory拆分优化方案

Dirty Memory会占用相当多的内存,但是有些部分只在运行时读写数据才需要,大约只有10%的类真正地更改了他们的方法、属性、协议,而且只有Swift类会使用Demangled Name字段,所以,我们可以拆分那些平时不用的部分为class_rw_ext_t,这可以将class_rw_t的大小减少一半,对于那些确实需要额外信息的类,可以分配这些扩展记录中的一个,并将它滑到类中供其使用(大约90%的类不需要这个扩展)。这样可以提升空间利用率。

image.png

image.png

1.1.4 通过终端实际验证微信和keynoteclass_rw_t占用的内存

// 微信
heap WeChat | egrep 'class_rw|COUNT'
// Keynote
heap Keynote | egrep 'class_rw|COUNT'
复制代码

微信验证结果:

image.png

Keynote验证结果:

image.png

数据分析:

  • 微信:class_rw_t占用内存192064字节,class_rw_ext_t占用内存21120字节,class_rw_ext_t仅为11%。
  • Keynote:class_rw_t占用内存256000字节,class_rw_ext_t占用内存38880字节,class_rw_ext_t仅为15%。

结论:
从上面的验证结果来看,class_rw_ext_t的需求量确实不高,这样的拆分特别有意义,最大程度的保证了内存使用效率。

2.成员变量和属性的区别

成员变量存储于class_ro_t中。

图解:

image.png

示例代码:

@interface XJPerson : NSObject
{
    NSString *_nickName;
    NSString *_hobby;
}

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

@end

@implementation XJPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XJPerson *person = [XJPerson alloc];
        NSLog(@"%@", person);
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
复制代码

通过clang编译成.cpp文件查看

clang -rewrite-objc main.m -o main.cpp
复制代码

图解:

image.png

.cpp文件中可以看出类被编译成了结构体,@property声明的属性被注释了,同时以_ + 属性名的方式生成成员变量,添加进了成员变量中,并且还默认生成了gettersetter方法。

结论:

  1. 属性在底层编译阶段会变成_ + 属性名的成员变量。
  2. 属性默认会自动生成gettersetter方法。

3.Type Encodings

Type Encodings官方文档

image.png

.cpp文件方法列表中,每个方法都会有Type Encodings,按照上表可以很清楚的理解各个方法Type Encodings的意思。
image.png

Type Encodings解析.jpeg

4. 属性的setter方法深入解析

细心的你应该已经发现了上面.cpp源码里namesetter方法和agesetter方法的不同之处,namesetter里面调用了void objc_setProperty (id, SEL, long, id, bool, bool)函数,而agesetter方法却是直接赋值给首地址加上成员变量age的地址偏移量。下面就探究下为什么会有这种分别。

代码示例:

@interface XJPerson : NSObject
{
    NSInteger _age;
    NSString *_hobby;
}

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) NSString *address;

@property (atomic, copy) NSString *nickName;

@property (atomic, strong) NSString *tel;

@property (nonatomic) NSObject *obj;

@property (atomic) CGFloat height;

@end

@implementation XJPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XJPerson *person = [XJPerson alloc];
        NSLog(@"%@", person);
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
复制代码

编译成.cpp文件,查看属性的setter方法:

image.png

4.1 非copy修饰的属性的gettersetter方法的原理

OC底层原理初探之对象的本质(三)alloc探索下这边文章里曾经分析过对象的getter/setter方法是通过对象首地址 + 内存平移的方式找到对象成员变量的内存地址,然后进行读值和取值。

getter方法图解:

getter.png

setter方法图解:

setter.png

4.2 copy修饰的属性的setter方法的深入解析

copy修饰的属性的getter方法与非copy修饰的属性没有什么区别,但是setter方法却调用了objc_setProperty(id, SEL, long, id, bool, bool)函数,在objc4-818.2源码里搜索此函数,发现函数对copy修饰的属性进行了相关的copy处理。

图解:
image.png

image.png

4.2.1 LLVM分析copy修饰的属性的setter方法

objc4-818.2源码里只有objc_setProperty函数的实现,却没有相关调用信息,通过llvm源码逆向查找确定了objc_setProperty函数的调用条件。

llvm源码逆向验证流程:objc_setProperty->getSetPropertyFn->GetPropertySetFunction->PropertyImplStrategy->IsCopy(判断)

图解(逆向查找):

image.png

结论:

  • copy修饰的属性,无论是是nonatomic还是atomic,编译器都会将setter方法重定向到objc_setProperty函数。

来都来了,不点个赞,不点个关注???

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享