底層相關(guān)面試題分析

dealloc 釋放的對象

首先查看dealloc的底層源碼

obj->rootDealloc();
if (isTaggedPointer()) return;  // fixme necessary?
// 根據(jù)isa中相關(guān)存儲格式進(jìn)行判斷
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance. 判斷是否還包含C++方法以及關(guān)聯(lián)對象
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

根據(jù)上述代碼得知:

  • 先查看isa相關(guān)信息來判斷是否還包含信息1是0否
  • object_dispose -> objc_destructInstance來判斷是否存在hasCxxDtor、hasAssociatedObjects
    • 如果存在C++方法,就從緩存中釋放object_cxxDestruct->object_cxxDestructFromClass->lookupMethodInClassAndLoadCache
    • 如果存在Associate,則從對應(yīng)的哈希表中查找然后依次刪除
ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }

主類與分類的同名方法調(diào)用順序

  • 如果方法為普通方法,包括initialize,這時(shí)會因?yàn)榫幾g時(shí)分類方法存儲在主類方法前面,故而優(yōu)先調(diào)用分類方法,
    • initialize方法也是主動調(diào)用,即第一次消息時(shí)調(diào)用,為了不影響整個(gè)load,可以將需要提前加載的數(shù)據(jù)寫到initialize
    • 其他分類方法通過attachCategories進(jìn)行添加在主類方法之前,但不會覆蓋主類方法
  • 如果為load方法,將先調(diào)用主類load方法,再調(diào)用分類load方法,可以在load_images中進(jìn)行驗(yàn)證
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // 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();
}
void prepare_load_methods(const headerType *mhdr)
{


    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[I];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

通過prepare_load_methods得知,先調(diào)用_getObjc2NonlazyClassList,然后才是_getObjc2NonlazyCategoryList
call_load_methods也是通過遍歷先call_class_loads然后call_category_loads

【面試-3】Runtime是什么?

runtime是由CC++匯編實(shí)現(xiàn)的一套API,為OC語言加入了 面向?qū)ο?/code>、以及運(yùn)行時(shí)的功能

運(yùn)行時(shí)是指將數(shù)據(jù)類型確定編譯時(shí) 推遲到了 運(yùn)行時(shí)

舉例:extensioncategory 的區(qū)別
平時(shí)編寫的OC代碼,在程序運(yùn)行的過程中,其實(shí)最終會轉(zhuǎn)換成runtimeC語言代碼, runtimeOC的幕后工作者

1、category 類別、分類

專門用來給類添加新的方法

不能給類添加成員屬性,添加了成員屬性,也無法取到

注意:其實(shí)可以通過runtime 給分類添加屬性,即屬性關(guān)聯(lián),重寫setter、getter方法

分類中用@property 定義變量,只會生成變量的setter、getter方法的聲明(可以編譯通過,是不能運(yùn)行),不能生成方法實(shí)現(xiàn)帶下劃線成員變量

2、extension 類擴(kuò)展

可以說成是特殊的分類 ,也可稱作 匿名分類

可以給類添加成員屬性,但是是私有變量

可以給類添加方法,也是私有方法

【面試-4】方法的本質(zhì),sel是什么?IMP是什么?兩者之間的關(guān)系又是什么?

方法的本質(zhì):發(fā)送消息,消息會有以下幾個(gè)流程

快速查找(objc_msgSend) - cache_t緩存消息中查找

慢速查找 - 遞歸自己|父類 -lookUpImpOrForward

查找不到消息:動態(tài)方法解析 - resolveInstanceMethod

消息快速轉(zhuǎn)發(fā) -forwardingTargetForSelector

消息慢速轉(zhuǎn)發(fā) - methodSignatureForSelector & forwardInvocation

sel方法編號 - 在read_images期間就編譯進(jìn)了內(nèi)存

imp函數(shù)實(shí)現(xiàn)指針 ,找imp就是找函數(shù)的過程

sel 相當(dāng)于 一本書的目錄title

imp 相當(dāng)于 書本的頁碼

查找具體的函數(shù)就是想看這本書具體篇章的內(nèi)容

  • 1、首先知道想看什么,即目錄 title - sel

  • 2、根據(jù)目錄找到對應(yīng)的頁碼 -imp

  • 3、通過頁碼去翻到具體的內(nèi)容

【面試-5】能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量

  • 1、不能向編譯后的得到的類中增加實(shí)例變量

  • 2、只要類沒有注冊到內(nèi)存還是可以添加

  • 3、可以添加屬性+方法
    【原因】:編譯好的實(shí)例變量存儲的位置是ro,一旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定了

【經(jīng)典面試-6】 [self class]和[super class]的區(qū)別以及原理分析

[self class]就是發(fā)送消息 objc_msgSend,消息接收者self,方法編號 class

[super class] 本質(zhì)就是objc_msgSendSuper,消息的接收者還是 self,方法編號 class,在運(yùn)行時(shí),底層調(diào)用的是_objc_msgSendSuper2【重點(diǎn)?。?!】

只是objc_msgSendSuper2 會更快,直接跳過self的查找

@interface LGTeacher : LGPerson
@implementation LGTeacher
- (instancetype)init{
    self = [super init];
    if (self) {
        NSLog(@"%@ - %@",[self class],[super class]);
    }
    return self;
}

通過clang編譯成cpp文件可以看出

static instancetype _I_LGTeacher_init(LGTeacher * self, SEL _cmd) {
    self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"));
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_6btl4svn6n5bqty614qwd7k80000gp_T_LGTeacher_d541e4_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
    }
    return self;
}

通過實(shí)現(xiàn)方法可以看出,[super class]并不是通過LGTeacher的父類LGPerson進(jìn)行調(diào)用,而消息接收者還是self即為LGTeacher,只是結(jié)構(gòu)為__rw_objc_super

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

通過底層源碼我們可以發(fā)現(xiàn),super_class只是內(nèi)部的一個(gè)參數(shù),并非改變消息接收者,因此一開始打印的 NSLog(@"%@ - %@",[self class],[super class]);結(jié)果都為LGTeacher

【面試-7】內(nèi)存平移問題

下面方法調(diào)用打印結(jié)果是否一致

- (void)saySomething{ 
    NSLog(@"%s",__func__);
}
在ViewController中調(diào)用
Class cls = [LGPerson class];
void  *kc = &cls;  //
[(__bridge id)kc saySomething];
LGPerson * person = [LGPerson alloc];
[person saySomething];

結(jié)果發(fā)現(xiàn)兩種打印是一致的,原因如下

2251862-1824c68a7400bb28-2.jpg

可以看出是通過personisa去獲取類的相關(guān)信息,personisa指向的LGPerson,而上面通過 *kc = &cls也可以看出kc指向的也是LGPerson,他們都是通過獲取LGPersonMethodList去查找saySomething方法,故而效果一致

@property (nonatomic, copy) NSString *kc_name;  // 12
- (void)saySomething{ 
    NSLog(@"%s - %@",__func__,self.kc_name);
}
2251862-6c51151a41e5b77b.jpg

如果進(jìn)行上述更改,只增加一個(gè)屬性的打印結(jié)果我們發(fā)現(xiàn):

  • 正常情況下,讀取屬性是通過內(nèi)存平移的方式進(jìn)行讀取,在person中存儲的事isa以及相關(guān)屬性,這時(shí)就可以通過person首地址平移8個(gè)單位獲取kc_name的地址從而進(jìn)行讀取,kc_name沒有賦值,打印為null可以理解
  • 如果是通過指針地址方式調(diào)用,發(fā)現(xiàn)打印了一個(gè)ViewController,這就很難理解,這時(shí)通過棧地址讀取的方式來查看相關(guān)內(nèi)容
void *sp  = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
    
for (long i = 0; i<count; i++) {
    void *address = sp - 0x8 * I;
    if ( i == 1) {
        NSLog(@"%p : %s",address, *(char **)address);
    }else{
        NSLog(@"%p : %@",address, *(void **)address);
    }
}
2251862-f956699a7af79eb4.jpg

通過打印可以得知棧中現(xiàn)在保存的相關(guān)信息,由于棧是從高地址向低地址進(jìn)行存儲

  • 當(dāng)進(jìn)行方法調(diào)用時(shí),首先傳入的是每個(gè)函數(shù)都會包含的隱藏參數(shù)(id self,sel _cmd)
  • 通過實(shí)踐可以得知結(jié)構(gòu)體成員內(nèi)部的壓棧情況是 低地址->高地址遞增的,故而如果是結(jié)構(gòu)體進(jìn)棧,則保存情況是后面變量先入棧
  • super通過clang查看底層的編譯,是objc_msgSendSuper,其第一個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體__rw_objc_super(self,class_getSuperclass)
    因此可以得出目前棧中所保存的信息為self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person

personLGPerson的關(guān)系是 person是以LGPerson為模板的實(shí)例化對象,即alloc有一個(gè)指針地址,指向isaisa指向LGPerson,它們之間關(guān)聯(lián)是有一個(gè)isa指向

由于kc也是指向LGPerson的關(guān)系,編譯器會認(rèn)為 kc也是LGPerson的一個(gè)實(shí)例化對象,即kc相當(dāng)于isa,即首地址,指向LGPerson,具有和person一樣的效果,簡單來說,我們已經(jīng)完全將編譯器騙過了,即kc也有kc_name。由于person查找kc_name是通過內(nèi)存平移8字節(jié),所以kc也是通過內(nèi)存平移8字節(jié)去查找kc_name,目前kc的首地址為0x7ffeec381098,平移0x8得出0x7ffeec3810a0,正好與棧中<ViewController: 0x7fac45514f50> 一致

【面試-8】 Runtime是如何實(shí)現(xiàn)weak的,為什么可以自動置nil

1、通過SideTable 找到我們的 weak_table

2、weak_table 根據(jù)referent找到或者創(chuàng)建 weak_entry_t

3、然后append_referrer(entry,referrer)將我的新弱引用的對象加進(jìn)去entry

4、最后 weak_entry_insert,把entry加入到我們的weak_table

2251862-edad5fa01465390e.png

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

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

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