前言
在底層研究 - 類的底層探索(上)中我們已經(jīng)探索得知了類對象本質(zhì)為objc_class結(jié)構(gòu)體,同時類中的信息都存儲在 class_rw_t 和 class_ro_t 中,那么接下來的重點就是探索下 class_rw_t 和class_ro_t以及使用runtime來獲取類的基本信息。
1、clean memory & dirty memory
在WWDC 2020有一段介紹提到了關(guān)于class_rw_t 和 class_ro_t 之間的關(guān)系,推薦觀看,十分牛掰??!o( ̄▽ ̄)d
接下來我們結(jié)合視頻和源碼進行探索下,首先我們要先知道2個概念:
-
clean memory
clean memory 是指加載后不會發(fā)生改變的內(nèi)存。它可以進行移除來節(jié)省更多的內(nèi)存空間,需要時再從磁盤加載。
class_ro_t就是屬于 clean memory。
-
dirty memory
dirty memory是指在運行時會發(fā)生改變的內(nèi)存。當類開始使用時,系統(tǒng)會在運行時為它分配一塊額外的內(nèi)存空間,也就是dirty memory,只要進程在運行,它就會一直存在,因此使用代價很高。
2、 ro & rw & rwe
-
class_ro_t
ro,也就是readonly,class_ro_t 是在編譯的時候生成的。當類在編譯的時候,類的屬性,實例?法,協(xié)議這些內(nèi)容就存在class_ro_t這個結(jié)構(gòu)體??了,這是?塊clean memory,不允許被修改,但可以在不使用的時候被刪除,需要的時候再從磁盤加載。

-
class_rw_t
rw,即readwrite,class_rw_t是在運?的時候?成的。當一個類第一次被使用時,rumtime會為其分配額外的內(nèi)存,即class_rw_t ,它會先將class_ro_t的內(nèi)容 剪切 到class_rw_t中存儲,整個內(nèi)存中其實只有一份。
通過 First Subclass 和 Next Sibling Class 指針實現(xiàn)了把類連接成一個樹狀結(jié)構(gòu),這就決定了runtime能夠遍歷當前使用的所有類

可以發(fā)現(xiàn)Methods、Properties、Protocols不僅存在于ro,也存在了rw中,這是因為在運行時它們是可以發(fā)生改變的,比如通過category添加方法或者通過runtime的api動態(tài)添加它們,系統(tǒng)需要去跟蹤這些內(nèi)容。

但按這種做法,會導致占用相當多的內(nèi)存,那怎么取縮小這些結(jié)構(gòu)呢?
在對bits的源碼探索中,我們發(fā)現(xiàn)了 rw 提供三個方法method()、properties()、protocols()來分別返回方法,屬性和協(xié)議,也就是說rw并沒有直接存儲這三者,而是存在于一個ro_or_rw_ext,因為rw屬于dirty memory,使用開銷大,因為把一些類的信息分離出來,能減小開銷。

-
class_rw_ext_t
class_rw_ext_t可以減少內(nèi)存的消耗。蘋果在wwdc2020??說過,只有?約10%左右的類需要動態(tài)修改。所以只有10%左右的類??需要?成class_rw_ext_t這個結(jié)構(gòu)體。這樣的話,可以節(jié)約很??部分內(nèi)存。對于動態(tài)修改的類可以通過class_rw_t結(jié)構(gòu)體中提供的ext()方法獲取class_rw_ext_t。

我們通過源碼也可以發(fā)現(xiàn),在methods方法中,也是先判斷的是否存在rwe,有則從rwe獲取方法,無則直接從ro中獲取。

那么 class_rw_ext_t 的?成的條件是什么呢?
- ?過runtime的Api進?動態(tài)修改的時候。
- 有分類的時候,且分類和本類都為?懶加載類的時候。實現(xiàn)了+load?法即為?懶加載類。
因此我們也能得到一個rw,ro,rwe的具體關(guān)系圖

3、runtime的基本使用
- 獲取類的成員變量
-(void)class_copyIvarList:(Class)pClass {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(pClass, &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
const char *cName = ivar_getName(ivar);
const char *cType = ivar_getTypeEncoding(ivar);
NSLog(@"name = %s type = %s",cName,cType);
}
free(ivars);
}
- 獲取類的屬性
-(void)class_copyPropertyList:(Class)pClass {
unsigned int outCount = 0;
objc_property_t *perperties = class_copyPropertyList(pClass, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = perperties[i];
const char *cName = property_getName(property);
const char *cType = property_getAttributes(property);
NSLog(@"name = %s type = %s",cName,cType);
}
free(perperties);
}
- 獲取類的方法
-(void)class_copyMethodList:(Class)pClass {
unsigned int outCount = 0;
Method *methods = class_copyMethodList(pClass, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSString *name = NSStringFromSelector(method_getName(method));
const char *cType = method_getTypeEncoding(method);
NSLog(@"name = %@ type = %s",name,cType);
}
free(methods);
}
- 交換方法的實現(xiàn),僅限當前類有效
-(void)class_replaceMethod{
//參數(shù):1.類Class 2.?法名SEL 3.?法的實現(xiàn)IMP 4.?法參數(shù)描述
//返回:BOOL
BOOL result = class_replaceMethod([self class], @selector(method1), [self
methodForSelector:@selector(method2)], NULL);
NSLog(@"class_replaceMethod:%d",result);
}
-(void)method1{
NSLog(@"method1");
}
-(void)method2{
NSLog(@"method2");
}
- 分類交換方法
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"classname %@", className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//在這里通過class_addMethod()的驗證,如果self實現(xiàn)了這個方法,class_addMethod()函數(shù)將會返回NO
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
稍微講解下:
dispatch_once
dyld雖然能夠保證調(diào)用Class的load時是線程安全的,但還是推薦使用dispatch_once做保護,防止極端情況下load被顯示強制調(diào)用時,重復交換(第一次交換成功,下次又換回來了...),造成邏輯混亂。class_addMethod
給指定Class添加一個SEL的實現(xiàn)(或者說是SEL和指定IMP的綁定),如果該SEL在父類中有實現(xiàn),則會添加一個覆蓋父類的方法,添加成功返回YES,SEL已經(jīng)存在或添加失敗返回NO。
因為iOS Runtime消息傳遞機制的影響,只執(zhí)行method_exchangeImplementations操作時可能會影響到父類的方法,這也是先使用class_addMethod的原因。class_replaceMethod
- 如果該Class不存在指定SEL,則class_replaceMethod的作用就和class_addMethod一樣;
- 如果該Class存在指定的SEL,則class_replaceMethod的作用就和method_setImplementation一樣。
4、總結(jié)
- clean memory是指加載后不會發(fā)生改變的內(nèi)存
- dirty memory是指在運行時會發(fā)生改變的內(nèi)存
- ro、rw、rwe具體流程
(1) 當編譯的時候,類會自動生成ro
(2) 當類被使用時,會生成rw,并把ro 剪切 到rw中
(3) 當類被修改時,rw內(nèi)會新增rwe,把允許修改的內(nèi)容轉(zhuǎn)移到rwe,讀取時優(yōu)先讀取rwe的內(nèi)容
(4) 內(nèi)存不足時,ro 可以在不使用的時候被移除 - 常用的runtime方法:
(1) 成員變量:class_copyIvarList、ivar_getName、ivar_getTypeEncoding
(2) 屬性:class_copyPropertyList、property_getName、property_getAttributes
(3) 方法:class_copyMethodList、method_getTypeEncoding、class_replaceMethod、class_addMethod、method_exchangeImplementations