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

前言

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

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é)構(gòu)體??了,這是?塊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é)構(gòu),這就決定了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é)構(gòu)呢?

在對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é)構(gòu)體。這樣的話,可以節(jié)約很??部分內(nèi)存。對于動態(tài)修改的類可以通過class_rw_t結(jié)構(gòu)體中提供的ext()方法獲取class_rw_ext_t。

內(nèi)存結(jié)構(gòu)之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的具體關(guān)系圖


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

  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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容