iOS 底層面試題

【面試-1】通過(guò) Asssociate 方法關(guān)聯(lián)的對(duì)象,需要在dealloc中釋放

當(dāng)對(duì)象釋放時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用dealloc

dealloc釋放步驟
  • 1、C++函數(shù)釋放:objc_cxxDestruct
  • 2、移除關(guān)聯(lián)屬性:_object_remove_assocations
  • 3、將弱引用自動(dòng)設(shè)置nil:weak_clear_no_lock(&table.weak_table, (id)this)
  • 4、引用計(jì)數(shù)清空:table.refcnts.erase(this)
  • 5、銷(xiāo)毀對(duì)象:free(obj)

由此可看出關(guān)聯(lián)對(duì)象不需要我們手動(dòng)移除,會(huì)在對(duì)象析構(gòu)(dealloc)時(shí)釋放

dealloc源碼
  • 在objc源碼中搜索dealloc實(shí)現(xiàn)

    image.png

  • _objc_rootDealloc源碼實(shí)現(xiàn),主要是對(duì)象進(jìn)行析構(gòu)

    image.png

  • rootDealloc源碼實(shí)現(xiàn)

    image.png

  • object_dispose源碼實(shí)現(xiàn),銷(xiāo)毀實(shí)例對(duì)象

    image.png

  • objc_destructInstance源碼實(shí)現(xiàn),移除關(guān)聯(lián)屬性

    image.png

  • _object_remove_assocations源碼,移除關(guān)聯(lián)屬性,主要是從全局哈希map中找到相關(guān)對(duì)象的迭代器,然后將迭代器中關(guān)聯(lián)屬性,徹底移除

    image.png

【面試-2】類(lèi)的方法 和 分類(lèi)方法 重名,方法調(diào)用順序

  • 如果是普通方法同名,包括initialize,都會(huì)先調(diào)用分類(lèi)方法

    • 因?yàn)?code>分類(lèi)的方法是類(lèi)realize 后attach進(jìn)去的,插在類(lèi)的方法的前面,所有優(yōu)先調(diào)用分類(lèi)的方法(不是分類(lèi)覆蓋分類(lèi))
    • initialize方法在第一次消息時(shí)主動(dòng)調(diào)用,為了不影響整個(gè)load,可以將提前加載的數(shù)據(jù)寫(xiě)到initialize
  • 如果同名方法時(shí)load方法,先調(diào)用主類(lèi)load,后加載分類(lèi)load(分類(lèi)之間看編譯順序)

【面試-3】Runtime是什么?

  • runtime是由C和C++匯編實(shí)現(xiàn)的一套API,為OC語(yǔ)言加入了面向?qū)ο螅约斑\(yùn)行時(shí)的功能

  • 將數(shù)據(jù)類(lèi)型的確定從 編譯時(shí) 推遲到了 運(yùn)行時(shí)

  • 平時(shí)編寫(xiě)的OC代碼,在程序運(yùn)行的過(guò)程中,其實(shí)最終會(huì)轉(zhuǎn)換成runtimeC語(yǔ)言代碼, runtime是OC的幕后工作者

【面試-4】方法的本質(zhì),sel是什么?IMP是什么??jī)烧咧g的關(guān)系又是什么?

  • 方法的本質(zhì):發(fā)送消息

    • 快速查找objc_msgSend :從cache_t緩存消息中查找
    • 慢速查找:遞歸查找自己和父類(lèi)lookUpImpOrForward
    • 查找不到消息
      • 動(dòng)態(tài)方法解析resolveInstanceMethod
      • 消息快速轉(zhuǎn)發(fā)forwardingTargetForSelector
      • 消息慢速轉(zhuǎn)發(fā)methodSignatureForSelector & forwardInvocation
  • sel方法編號(hào) -- 在read_images 期間就編譯進(jìn)了內(nèi)存,相當(dāng)于一本書(shū)的目錄名字

  • imp函數(shù)實(shí)現(xiàn)指針,找imp就是找函數(shù)的過(guò)程,相當(dāng)于書(shū)本的頁(yè)碼

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

    • 1、首先知道想看什么,即目錄title--sel
    • 2、根據(jù)目錄找到對(duì)應(yīng)的頁(yè)碼,即imp
    • 3、通過(guò)頁(yè)碼去找到具體內(nèi)容

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

  • 1、不能向編譯后得到的類(lèi)中增加實(shí)例變量,因?yàn)榫幾g好的實(shí)例變量存儲(chǔ)的位置是ro,一旦編譯完成,內(nèi)存結(jié)構(gòu)就確定了
  • 2、只要類(lèi)沒(méi)有注冊(cè)到內(nèi)存就可以添加,可以添加屬性+方法

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

  • [self class]就是發(fā)送消息objc_msgSend,消息接收者是self,方法編號(hào)是class
  • [super class]本質(zhì)是objc_msgSendSuper,消息接收者還是self,方法編號(hào)還是class,super只是一個(gè)關(guān)鍵字,在運(yùn)行時(shí),底層調(diào)用的是_objc_msgSendSuper2,_objc_msgSendSuper2速度更快,會(huì)跳過(guò)self的查找
image.png
[self class]
  • [self class]中的class源碼
- (Class)class {
    return object_getClass(self);
}

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

其底層是獲取對(duì)象的isa,當(dāng)前的對(duì)象是LGTeacher,那么isa就是同名的LGTeacher,所有[self class]打印的是LGTeacher

[super class]
  • super是語(yǔ)法的關(guān)鍵字,我們可以通過(guò)命令行clang -rewrite-objc LGTeacher.m -o LGTeacher.cpp來(lái)查看super本質(zhì),這是編譯時(shí)的底層源碼,其中第一個(gè)參數(shù)是消息接收者,另外一個(gè)是__rw_objc_super結(jié)構(gòu)

    image.png

  • 在底層源碼中搜索__rw_objc_super,是一個(gè)中間結(jié)構(gòu)體

    image.png

  • 在objc中搜索objc_msgSendSuper,看到了影藏參數(shù)

    image.png

  • 繼續(xù)搜索struct objc_super

    image.png

通過(guò)clang的底層編譯代碼可知,當(dāng)前的消息的接收者==self,而self==LGTeacher,所以[super class]進(jìn)入class方法源碼后,其中self是init后的實(shí)例對(duì)象,實(shí)例對(duì)象的isa指向本類(lèi),即消息接收者是LGTeacher本類(lèi)

【面試-7】?jī)?nèi)存平移問(wèn)題

LGPerson中有一個(gè)屬性kc_name 和一個(gè)實(shí)例方法saySomething,通過(guò)下面代碼,能否調(diào)用實(shí)例方法?

- (void)saySomething{
    NSLog(@"%s",__func__);
}

//下面這兩種方式調(diào)用
//方式一
Class cls = [LGPerson class];
void  *kc = &cls; 
[(__bridge id)kc saySomething]; 

//方式二:常規(guī)調(diào)用
LGPerson *person = [LGPerson alloc];
 [person saySomething];

代碼分析
  • [person saySomething]的本質(zhì)是對(duì)象發(fā)送消息,

    • personisa指向類(lèi)LGPerson,person的首地址 指向 LGPerson的首地址,我們可以通過(guò)內(nèi)存平移找到cache,在cache中查找方法
      image.png
  • [(__bridge id)kc saySomething]中的kc是來(lái)自于LGPerson 這個(gè)類(lèi),然后有一個(gè)指針kc,將其指向LGPerson的首地址

    image.png

所以,person是指向LGPerson類(lèi)的結(jié)構(gòu),kc也是指向LGPerson類(lèi)的結(jié)構(gòu),然后都是在LGPerson中的methodList中查找方法

image.png

修改:saySomething里面有屬性 self.kc_name 的打印

代碼如下所示

- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.kc_name);
}

//下面這兩種方式調(diào)用
//方式一
Class cls = [LGPerson class];
void  *kc = &cls; 
[(__bridge id)kc saySomething]; 

//方式二:常規(guī)調(diào)用
LGPerson *person = [LGPerson alloc];
 [person saySomething];
  • 打印結(jié)果
    • 方式一:kc的調(diào)用打印的kc_name<ViewController: 0x7fe29170b560>
    • 方式二:person方式的調(diào)用打印的kc_name(null)
      image.png

為什么會(huì)出現(xiàn)打印不一致的情況?

其中personkc_name 是由于 self指向person的內(nèi)存結(jié)構(gòu),然后通過(guò)內(nèi)存平移8字節(jié),取出去kc_name,即self指針首地址平移8字節(jié)獲得

image.png

  • 【方式一】其中kc表示8字節(jié)指針,self.kc_name的獲取,相當(dāng)于kc首地址的指針也需要平移8字節(jié)找到kc_name,那么此時(shí)的kc的指針地址是多少?平移8字節(jié)獲取的是什么?

    • kc是一個(gè)指針,存在中,因?yàn)闂J?code>先進(jìn)后出的結(jié)構(gòu),參數(shù)的傳入是一個(gè)不斷壓棧的過(guò)程
      • 其中影藏參數(shù)也會(huì)壓入棧,每個(gè)函數(shù)都會(huì)有兩個(gè)隱藏參數(shù)(id self, sel _cmd),可以通過(guò)clang查看底層編譯
      • 隱藏參數(shù)壓棧的過(guò)程,其地址是遞減的,而棧是從高地址->低地址 分配的,即在棧中,參數(shù)會(huì)從前往后一直壓
      • 因?yàn)?code>objc_msgSendSuper中第一個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體__rw_objc_super(self,class_getSuperclass)那么結(jié)構(gòu)體中的屬性是如何壓棧的?可以通過(guò)自定義一個(gè)結(jié)構(gòu)體,判斷結(jié)構(gòu)體內(nèi)部成員的壓棧情況
        image.png

        從圖可知20是先加入的,再加入10,因此結(jié)構(gòu)體內(nèi)部的壓棧情況是低地址->高地址遞增的,棧中結(jié)構(gòu)體內(nèi)部成員是反向壓入棧的
  • 所以到目前為止,棧中高地址->低地址的順序是:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person

    • self和_cmdviewDidLoad方法的兩個(gè)隱藏參數(shù),是高地址->低地址正向壓棧
    • class_getSuperClassselfobjc_msgSendSuper2中的結(jié)構(gòu)體成員,即低地址->高地址反向壓棧

我們可以通過(guò)下面這段代碼打印棧的存儲(chǔ)是否如上面所說(shuō)

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);
    }
}

運(yùn)行結(jié)果如下


image.png

其中為什么class_getSuperclass是ViewController,因?yàn)閛bjc_msgSendSuper2返回的是當(dāng)前類(lèi),兩個(gè)self,并不是同一個(gè)self,而是棧的指針不同,但是指向同一片內(nèi)存空間

  • [(__bridge id)kc saySomething]調(diào)用時(shí),此時(shí)kc是LGPerson: 0x7ffeec381098,所以saySomething方法中傳入的self==LGPerson,但并不是我通常認(rèn)為的LGPerson,即LGPerson: 0x7ffeec381098,是LGPerson的實(shí)例對(duì)象,可以通過(guò)LGPerson的地址內(nèi)存平移8字節(jié)獲得方法地址

    • 普通person流程:person -> kc_name - 內(nèi)存平移8字節(jié)
    • kc流程:0x7ffeec381098 + 0x80 -> 0x7ffeec3810a0,即為self,指向<ViewController: 0x7fac45514f50>,如下圖所示
      image.png

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

而kc也是指向LGPerson的關(guān)系,編譯器會(huì)認(rèn)為 kc也是LGPerson的一個(gè)實(shí)例化對(duì)象,即kc相當(dāng)于isa,即首地址,指向LGPerson,具有和person一樣的效果,簡(jiǎn)單來(lái)說(shuō),我們已經(jīng)完全將編譯器騙過(guò)了,即kc也有kc_name。由于person查找kc_name是通過(guò)內(nèi)存平移8字節(jié),所以kc也是通過(guò)內(nèi)存平移8字節(jié)去查找kc_name

哪些東西在棧里 哪些在堆里
  • alloc的對(duì)象 都在

  • 指針、對(duì)象中,例如person指向的空間中,person所在的空間

  • 臨時(shí)變量

  • 屬性值,屬性隨對(duì)象是在

  • 是從小到大,即低地址->高地址
  • 是從大到小,即從高地址->低地址分配
    • 函數(shù)隱藏參數(shù)會(huì)從前往后一直壓,即 從高地址->低地址 開(kāi)始入棧
    • 結(jié)構(gòu)體內(nèi)部的成員是從低地址->高地址
  • 一般情況下,內(nèi)存地址有如下規(guī)則
    • 0x60 開(kāi)頭表示在
    • 0x70 開(kāi)頭的地址表示在
    • 0x10 開(kāi)頭的地址表示在全局區(qū)域

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

  • 1、通過(guò)SideTable 找到我們的 weak_table
  • 2、weak_table 根據(jù)referent找到或者創(chuàng)建 weak_entry_t
  • 3、然后append_referrer(entry,referrer)將我的新弱引用的對(duì)象加進(jìn)去
  • 4、最后 weak_entry_insert,加入到我們的weak_table

底層源碼調(diào)用流程如下圖所示


image.png
?著作權(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)容