底層研究 - 類的底層探索(中)

前言

底層研究 - 類的底層探索(上)中我們已經(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

Advancements in the Objective-C runtime

接下來我們結(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_ro_t

  • 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 SubclassNext Sibling Class 指針實現(xiàn)了把類連接成一個樹狀結(jié)構讶请,這就決定了runtime能夠遍歷當前使用的所有類

class_rw_t

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

rw擁有Methods晒来、Properties钞诡、Protocols

但按這種做法,會導致占用相當多的內(nèi)存湃崩,那怎么取縮小這些結(jié)構呢荧降?

在對bits的源碼探索中,我們發(fā)現(xiàn)了 rw 提供三個方法method()攒读、properties()朵诫、protocols()來分別返回方法,屬性和協(xié)議薄扁,也就是說rw并沒有直接存儲這三者剪返,而是存在于一個ro_or_rw_ext废累,因為rw屬于dirty memory,使用開銷大脱盲,因為把一些類的信息分離出來邑滨,能減小開銷。

method()钱反、properties()掖看、protocols()

  • 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。

內(nèi)存結(jié)構之class_rw_ext_t

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

rwe的使用

那么 class_rw_ext_t 的?成的條件是什么呢级乐?

  1. ?過runtime的Api進?動態(tài)修改的時候疙咸。
  2. 有分類的時候,且分類和本類都為?懶加載類的時候风科。實現(xiàn)了+load?法即為?懶加載類撒轮。

因此我們也能得到一個rw,ro贼穆,rwe的具體關系圖


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

  1. 如果該Class不存在指定SEL,則class_replaceMethod的作用就和class_addMethod一樣吞获;
  2. 如果該Class存在指定的SEL况凉,則class_replaceMethod的作用就和method_setImplementation一樣。

4各拷、總結(jié)

  1. clean memory是指加載后不會發(fā)生改變的內(nèi)存
  2. dirty memory是指在運行時會發(fā)生改變的內(nèi)存
  3. 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 可以在不使用的時候被移除
  4. 常用的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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绿语,一起剝皮案震驚了整個濱河市秃症,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吕粹,老刑警劉巖伍纫,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昂芜,居然都是意外死亡莹规,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門泌神,熙熙樓的掌柜王于貴愁眉苦臉地迎上來良漱,“玉大人舞虱,你說我怎么就攤上這事∧甘校” “怎么了矾兜?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長患久。 經(jīng)常有香客問我椅寺,道長,這世上最難降的妖魔是什么蒋失? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任返帕,我火速辦了婚禮,結(jié)果婚禮上篙挽,老公的妹妹穿的比我還像新娘荆萤。我一直安慰自己,他們只是感情好铣卡,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布链韭。 她就那樣靜靜地躺著,像睡著了一般煮落。 火紅的嫁衣襯著肌膚如雪敞峭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天蝉仇,我揣著相機與錄音儡陨,去河邊找鬼。 笑死量淌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嫌褪。 我是一名探鬼主播呀枢,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笼痛!你這毒婦竟也來了裙秋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缨伊,失蹤者是張志新(化名)和其女友劉穎摘刑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刻坊,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡枷恕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谭胚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徐块。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡未玻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胡控,到底是詐尸還是另有隱情扳剿,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布昼激,位于F島的核電站庇绽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏橙困。R本人自食惡果不足惜瞧掺,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纷宇。 院中可真熱鬧夸盟,春花似錦、人聲如沸像捶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拓春。三九已至释簿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硼莽,已是汗流浹背庶溶。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留懂鸵,地道東北人偏螺。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像匆光,于是被迫代替她去往敵國和親套像。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容