RunTime的應(yīng)用及相關(guān)的面試問(wèn)題

本次主要記錄幾個(gè)問(wèn)題
1、runtime是什么
2、method-swizzling使用及使用過(guò)程中的坑點(diǎn)
3、isKinkOfClass和isMemmberOfClass的使用
4、[self class]和[super class]的區(qū)別以及原理分析
5、runtime Associate方法的關(guān)聯(lián)對(duì)象,及什么時(shí)間釋放
6、__weak對(duì)象的存儲(chǔ)及釋放

1、runtime是什么

這個(gè)就說(shuō)的比較寬泛一些,runtime是一套由C & C++編寫(xiě)的一套api,給我們OC提供運(yùn)行時(shí)的功能,研究runtime對(duì)其他底層的學(xué)習(xí)會(huì)有一個(gè)比較有利的幫助

2、method-swizzling使用及使用過(guò)程中的坑點(diǎn)

Class class = [self class];

// 原方法名和替換方法名
SEL originalSelector = @selector(isEqualToString:);
SEL swizzledSelector = @selector(swizzle_IsEqualToString:);

// 原方法和替換方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// 如果當(dāng)前類沒(méi)有原方法的實(shí)現(xiàn)IMP,先調(diào)用class_addMethod來(lái)給原方法添加實(shí)現(xiàn)
BOOL didAddMethod = class_addMethod(class,
                                    originalSelector,
                                  method_getImplementation(swizzledMethod),
                                    method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {// 添加方法實(shí)現(xiàn)IMP成功后,替換方法實(shí)現(xiàn)
    class_replaceMethod(class,
                        swizzledSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
} else { // 有原方法,交換兩個(gè)方法的實(shí)現(xiàn)
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

1、方法交換,一般我們是在+ (void)load方法中進(jìn)行方法交換,為什么
1)執(zhí)行比較早,在main函數(shù)之前就已經(jīng)執(zhí)行
2)自動(dòng)執(zhí)行,不需要手動(dòng)調(diào)用,這里的調(diào)用 不是通過(guò)objc_msgSend調(diào)用而是直接通過(guò)指針找到相應(yīng)的imp調(diào)用
3)唯一性,調(diào)用順序?yàn)?先 父類 -> 子類 -> 分類
》這里有一個(gè)優(yōu)化點(diǎn),App啟動(dòng)的時(shí)候 main函數(shù)之前 若是當(dāng)前類 實(shí)現(xiàn)了load方法 就會(huì)調(diào)用該方法,若是load中執(zhí)行了耗時(shí)操作,就會(huì)延長(zhǎng)App的啟動(dòng)時(shí)間,所以 我們減少App的啟動(dòng)時(shí)間的一個(gè)優(yōu)化點(diǎn)就是 不要在load方法中進(jìn)行耗時(shí)操作,也不要隨便重寫(xiě)load方法
》還有一個(gè)面試題是 load方法是否可以被交換即被hook
答案是可以的,但是成本會(huì)比較高
我們看load方法的調(diào)用堆棧


load方法調(diào)用堆棧

動(dòng)態(tài)鏈接器dyld,完成對(duì)二進(jìn)制文件(動(dòng)態(tài)庫(kù)、可執(zhí)行文件)的初始化后通過(guò)回調(diào)函數(shù)_dyld_objc_notify_register,調(diào)用load_images和call_load_method實(shí)現(xiàn)load方法的調(diào)用,

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

load_images通過(guò)prepare_load_methods將所有類的load方法加入到list中,包括父類和分類,這里記錄的是所有有l(wèi)oad方法的類 和 load方法的imp,之后通過(guò)call_load_methods調(diào)用所有的load方法
有上述問(wèn)題 若是我們需要hook A的load方法 必須在A的load方法被加載進(jìn)入load方法列表之前進(jìn)行hook,即我們把hook A的load方法的代碼放在B中,然后保證B動(dòng)態(tài)庫(kù)在A之前被鏈接,這樣就可以進(jìn)行hook了

isKinkOfClass和isMemmberOfClass的使用

對(duì)象、類對(duì)象、元類關(guān)系圖

這里首先我們看一下下面代碼的打印,先看實(shí)例方法,這個(gè)比較簡(jiǎn)單

- (void)test4 {
    
    NSObject *object = [[NSObject alloc] init];
    Person *person = [[Person alloc] init];
    
    bool o1 = [object isKindOfClass:[NSObject class]];
    bool o2 = [object isMemberOfClass:[NSObject class]];
    bool p1 = [person isKindOfClass:[Person class]];
    bool p2 = [person isMemberOfClass:[Person class]];
    // 1-1-1-1
    NSLog(@"%d-%d-%d-%d",o1,o2,p1,p2);
    
}

-isKindOfClass

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 我們可以看到,對(duì)象方法的 for循環(huán) 初始值 變成了 [self class],也就是從當(dāng)前類開(kāi)始找superclass繼承鏈。

  • 所以 [(id)[NSObject alloc] isKindOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isKindOfClass:[DZPerson class]] 都為 YES

-isMemberOfClass

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

  • -isMemberOfClass 對(duì)象方法更是簡(jiǎn)單了,直接就是判斷當(dāng)前類和傳入類是否相等。
  • [(id)[NSObject alloc] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isMemberOfClass:[DZPerson class]] 自然都是 YES。

再看類方法

- (void)test5 {
    
    Class object = [NSObject class];
    Class person = [Person class];
    
    bool o1 = [object isKindOfClass:[NSObject class]];
    bool o2 = [object isMemberOfClass:[NSObject class]];
    bool p1 = [person isKindOfClass:[Person class]];
    bool p2 = [person isMemberOfClass:[Person class]];
    
    // 1-0-0-0
    NSLog(@"===class=%d-%d-%d-%d",o1,o2,p1,p2);
    
}

為什么呢?我們看一下isKindOfClass的源碼

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

我們可以看出

  • Class tcls = object_getClass((id)self);

    從源碼可以看到,self 是類本身,object_getClass((id)self) 則是獲取 isa,而 isa 是指向元類的,所以 tcls 實(shí)際上是當(dāng)前類的元類。
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass)

  • for循環(huán)實(shí)際上就是從當(dāng)前類的元類開(kāi)始,沿著繼承鏈中的 superclass 一直向上循環(huán),在如下 isa指向圖 中標(biāo)注部分,NSObject元類 的父類是 NSObject。所以在第二次循環(huán)的時(shí)候,NSObject元類 的 superclass 是本身NSObject。
  • 但是 DZPerson元類 的繼承鏈?zhǔn)荄ZPerson元類 -> NSObject元類 -> NSObject,所以在 DZPerson元類 的繼承鏈上永遠(yuǎn)不會(huì)有自身DZPerson。
  • 因此 [(id)[NSObject class] isKindOfClass:[NSObject class]] = YES ,而 [(id)[DZPerson class] isKindOfClass:[DZPerson class]] == NO。

再看 isMemberOfClass的源碼

+ (BOOL)isMemberOfClass:(Class)cls {   
    return object_getClass((id)self) == cls;
}

  • 從源碼中可以看到,代碼是直接判斷當(dāng)前類的元類是否等于傳入類。
  • 所以 [(id)[NSObject class] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson class] isMemberOfClass:[DZPerson class]]中,NSObject元類 不等于 NSObject,DZPerson元類 也不等于 DZPerson,結(jié)果自然都是 NO。

[self class]和[super class]的區(qū)別以及原理分析

[self class]

- (Class)class {
    return object_getClass(self);
}
///
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

也就是獲取當(dāng)前類的isa指向 實(shí)例的isa指向類
[super class]
本質(zhì)上是調(diào)用objc_msgSendSuper

OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
第一個(gè)參數(shù)為一個(gè)objc_super的結(jié)構(gòu)體
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};
那么[super class]就等價(jià)于
struct objc_super lg_super = {
//我們這里研究對(duì)象為當(dāng)前對(duì)象所以消息接收這位self
   self,
   class_getSuperclass([self class]),
};
objc_msgSendSuper(&lg_super,@selector(class))

消息接受者 還是self 即 還是會(huì)打印實(shí)例的類

  • [self class] 就是發(fā)送消息objc_msgSend,消息接受者是 self 方法編號(hào):class

  • [super class] 本質(zhì)就是objc_msgSendSuper, 消息的接受者還是 self 方法編號(hào):class 只是objc_msgSendSuper 會(huì)更快 直接跳過(guò) self 的查找

不能向編譯后的類中添加實(shí)例變量,但是可以向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量

  • 類編譯后只讀結(jié)構(gòu)體class_ro_t就被確定了,運(yùn)行時(shí)不可修改
  • ro結(jié)構(gòu)體中的iver_list也是不可修改的,并且instanceSize決定了創(chuàng)建對(duì)象時(shí)需要的空間大小
  • 運(yùn)行時(shí)添加實(shí)例變量 必須在objc_allocateClassPair和objc_registerClassPair之間調(diào)用class_addIver

runtime Associate方法的關(guān)聯(lián)對(duì)象,及什么時(shí)間釋放

  • 需要調(diào)用objc_setAssociatedObject,詢問(wèn)是否存在全局的AssociationsManager有就獲取,沒(méi)有就創(chuàng)建,然后跟拒當(dāng)前類獲取 ObjectAssociationMap,沒(méi)有就創(chuàng)建一個(gè),存入我們的關(guān)聯(lián)對(duì)象
  • _object_get_associative_reference,獲取也是同樣的邏輯 全局AssociationsManager,根據(jù)當(dāng)前類獲取ObjectAssociationMap,再根據(jù)關(guān)聯(lián)對(duì)象的key獲取值
  • 刪除關(guān)聯(lián)對(duì)象 ,在主類dealloc時(shí)調(diào)用析構(gòu)函數(shù) objc_destructInstance,然后看當(dāng)前類是否存在析構(gòu),存在的話調(diào)用_object_remove_assocations,刪除析構(gòu)

__weak對(duì)象的存儲(chǔ)及釋放

image.png
  • 從全局的SideTables中,利用對(duì)象本身的地址取得該對(duì)象的弱引用表weak_table
  • 如果有分配新值,則檢查新值對(duì)應(yīng)的類是否進(jìn)行過(guò)初始化,如果沒(méi)有則就地初始化weak_register_no_lock
  • 若是有舊值,則需要把舊值對(duì)應(yīng)的弱引用表進(jìn)行注銷weak_unregister_no_lock
  • 將新值注冊(cè)到對(duì)應(yīng)的弱引用表中,將isa.weakly_referenced設(shè)置為true,表示該類有弱引用變量,釋放時(shí)需要清空弱引用表

鏈接弱引用表

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;// 被引用的對(duì)象
    objc_object **referrer = (objc_object **)referrer_id;// 弱引用變量

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 確保弱引用對(duì)象是否可行
    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    // 如果正在釋放中,則根據(jù) crashIfDeallocating 判斷是否觸發(fā) crash
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    // 每個(gè)對(duì)象對(duì)應(yīng)的一個(gè)弱引用記錄
    weak_entry_t *entry;
    
    // 如果當(dāng)前表中有該對(duì)象的記錄則直接加入該 weak 表中對(duì)應(yīng)記錄
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        
        // 沒(méi)有在 weak 表中找到對(duì)應(yīng)記錄,則新建一個(gè)記錄
        weak_entry_t new_entry(referent, referrer);
        
        // 查看是否需要擴(kuò)容
        weak_grow_maybe(weak_table);
        
        // 將記錄插入 weak 表中
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

上述代碼主要功能如下:

  • 判斷被指向?qū)ο笫欠窨尚?,也就是判斷其是否正在釋放,并且?huì)根據(jù)crashIfDeallocating判斷是否觸發(fā)crash。
  • 在weak_table中檢測(cè)是否有被指向?qū)ο蟮膃ntry,如果有的話,直接將該弱引用變量指針加入到該entry中
  • 如果沒(méi)有找到對(duì)應(yīng)的entry,新建一個(gè)entry,并將弱引用變量指針地址加入entry,同時(shí)檢查weaktable是否擴(kuò)容。

移除

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;
    // 指向?qū)ο鬄榭罩苯臃祷?    if (!referent) return;

    // 在weak表中查找
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        
        // 找到相應(yīng)記錄后,將該引用從記錄中移除。
        remove_referrer(entry, referrer);
        // 移除后檢查該記錄是否為空
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            // 不為空 將標(biāo)記記錄為false
            empty = false;
        }
        else {
            // 對(duì)比到記錄的每一行
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        // 如果當(dāng)前記錄為空則移除記錄

        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}


這段代碼的主要流程

  • 從weak_table中根據(jù)找到被引用對(duì)象對(duì)應(yīng)的entry,然后將弱引用變量指針referrer從entry中移除。
  • 移除弱引用變量指針referrer之后,檢查entry是否為空,如果為空將其從weak_table中移除
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // 釋放 entry 中的所有弱引用
    if (entry->out_of_line()) free(entry->referrers);
    // 置空指針
    bzero(entry, sizeof(*entry));
    // 更新 weak_table 對(duì)象數(shù)量,并檢查是否可以縮減表容量
    weak_table->num_entries--;
    weak_compact_maybe(weak_table);
}

  • 釋放entry和其中的弱引用變量。
  • 更新 weak_table 對(duì)象數(shù)量,并檢查是否可以縮減表容量

entry 和 referrer
entry以及比較熟悉了,一個(gè)對(duì)象的弱引用記錄,referrer則是代表弱引用變量,每次被弱引用時(shí),都會(huì)將弱引用變量指針referrer加入entry中,而當(dāng)原對(duì)象被釋放時(shí),會(huì)將entry清空并移除

從entry移除referrer的步驟:

out_of_line為false時(shí),從有序數(shù)組inline_referrers中查找并移除。
out_of_line為true時(shí),從哈希表中查找并移除

dealloc
當(dāng)被引用的對(duì)象被釋放后,會(huì)去檢查isa.weakly_referenced標(biāo)志位,每個(gè)被弱引用的對(duì)象weakly_referenced標(biāo)志位都為true。

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    // 根據(jù)指針獲取對(duì)應(yīng) Sidetable
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        // 存在弱引用
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}


從上面的代碼可以看出,在對(duì)象釋執(zhí)行dealloc函數(shù)時(shí),會(huì)檢查isa.weakly_referenced標(biāo)志位,然后判斷是否要清理weak_table中的entry。

這最后還是走到了前面的remove。

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

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

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