【面試-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中
- 因?yàn)?code>分類(lèi)的方法是類(lèi)realize 后attach進(jìn)去的,插在類(lèi)的方法的前面,所有
-
如果同名方法時(shí)
load方法,先調(diào)用主類(lèi)load,后加載分類(lèi)load(分類(lèi)之間看編譯順序)- 原理可以查看iOS 類(lèi)的加載(下)文章中的
load_images原理分析
- 原理可以查看iOS 類(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)換成
runtime的C語(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
- 動(dòng)態(tài)方法解析
- 快速查找
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的查找

[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ā)送消息,-
person的isa指向類(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中查找方法

修改: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)打印不一致的情況?
其中person的kc_name 是由于 self指向person的內(nèi)存結(jié)構(gòu),然后通過(guò)內(nèi)存平移8字節(jié),取出去kc_name,即self指針首地址平移8字節(jié)獲得

-
【方式一】其中
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和_cmd是viewDidLoad方法的兩個(gè)隱藏參數(shù),是高地址->低地址正向壓棧的 -
class_getSuperClass和self為objc_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é)果如下

其中為什么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
- 普通person流程:
其中 person 與 LGPerson的關(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、然后a
ppend_referrer(entry,referrer)將我的新弱引用的對(duì)象加進(jìn)去 - 4、最后
weak_entry_insert,加入到我們的weak_table
底層源碼調(diào)用流程如下圖所示















