前言
在底層研究 - 類的底層探索(上)中我們已經(jīng)探索得知了類對象本質(zhì)為objc_class結(jié)構體,同時類中的信息都存儲在 class_rw_t 和 class_ro_t 中兢孝,那么接下來的重點就是探索下 class_rw_t 和class_ro_t以及使用runtime來獲取類的基本信息。
1、clean memory & dirty memory
在WWDC 2020有一段介紹提到了關于class_rw_t 和 class_ro_t 之間的關系,推薦觀看,十分牛掰5纷鳌!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é)構體??了党觅,這是?塊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é)構讶请,這就決定了runtime能夠遍歷當前使用的所有類
可以發(fā)現(xiàn)Methods、Properties屎媳、Protocols不僅存在于ro夺溢,也存在了rw中,這是因為在運行時它們是可以發(fā)生改變的剿牺,比如通過category添加方法或者通過runtime的api動態(tài)添加它們企垦,系統(tǒng)需要去跟蹤這些內(nèi)容。
但按這種做法,會導致占用相當多的內(nèi)存湃崩,那怎么取縮小這些結(jié)構呢荧降?
在對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é)構體。這樣的話尚卫,可以節(jié)約很??部分內(nèi)存归榕。對于動態(tài)修改的類可以通過class_rw_t結(jié)構體中提供的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的具體關系圖
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