iOS-底層原理 20:OC底層面試解析

iOS 底層原理 文章匯總

【面試-1】Runtime Asssociate方法關(guān)聯(lián)的對象嗦随,需要在dealloc中釋放?

當我們對象釋放時,會調(diào)用dealloc

  • 1敬尺、C++函數(shù)釋放 :objc_cxxDestruct
  • 2枚尼、移除關(guān)聯(lián)屬性:_object_remove_assocations
  • 3、將弱引用自動設(shè)置nil:weak_clear_no_lock(&table.weak_table, (id)this);
  • 4砂吞、引用計數(shù)處理:table.refcnts.erase(this)
  • 5署恍、銷毀對象:free(obj)

所以,關(guān)聯(lián)對象不需要我們手動移除蜻直,會在對象析構(gòu)即dealloc時釋放

dealloc 源碼

dealloc的源碼查找路徑為:dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose(釋放對象)-> objc_destructInstance -> _object_remove_assocations

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

    image

  • 進入_objc_rootDealloc源碼實現(xiàn)盯质,主要是對對象進行析構(gòu)

    image

  • 進入rootDealloc源碼實現(xiàn),發(fā)現(xiàn)其中有關(guān)聯(lián)屬性時設(shè)置bool值袭蝗,當有這些條件時唤殴,需要進入else流程

    image

  • 進入object_dispose源碼實現(xiàn),主要是銷毀實例對象

    image

  • 進入objc_destructInstance源碼實現(xiàn)到腥,在這里有移除關(guān)聯(lián)屬性的方法

    image

  • 進入_object_remove_assocations源碼朵逝,關(guān)聯(lián)屬性的移除,主要是從全局哈希map中找到相關(guān)對象的迭代器乡范,然后將迭代器中關(guān)聯(lián)屬性配名,從頭到尾的移除

    image

【面試-2】方法的調(diào)用順序

類的方法 和 分類方法 重名啤咽,如果調(diào)用,是什么情況渠脉?

  • 如果同名方法是普通方法宇整,包括initialize -- 先調(diào)用分類方法

    • 因為分類的方法是在類realize之后 attach進去的,插在類的方法的前面芋膘,所以優(yōu)先調(diào)用分類的方法(注意:不是分類覆蓋主類A矍唷!)

    • initialize方法什么時候調(diào)用为朋? initialize方法也是主動調(diào)用臂拓,即第一次消息時調(diào)用,為了不影響整個load习寸,可以將需要提前加載的數(shù)據(jù)寫到initialize

  • 如果同名方法是load方法 -- 先 主類load胶惰,后分類load(分類之間,看編譯的順序)

image

【面試-3】Runtime是什么霞溪?

  • runtime是由C和C++匯編實現(xiàn)的一套API孵滞,為OC語言加入了 面向?qū)ο蟆⒁约斑\行時的功能

  • 運行時是指將數(shù)據(jù)類型的確定由編譯時 推遲到了 運行時

    • 舉例:extension 和 category 的區(qū)別
  • 平時編寫的OC代碼鸯匹,在程序運行的過程中坊饶,其實最終會轉(zhuǎn)換成runtime的C語言代碼, runtime是OC的幕后工作者

1殴蓬、category 類別幼东、分類

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

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

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

  • 分類中用@property 定義變量,只會生成變量的setter球散、getter方法的聲明尿赚,不能生成方法實現(xiàn) 和 帶下劃線的成員變量

2、extension 類擴展

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

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

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

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

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

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

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

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

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

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

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

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

  • sel 相當于 一本書的目錄title

  • imp 相當于 書本的頁碼

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

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

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

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

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

  • 1误算、不能向編譯后的得到的類中增加實例變量

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

  • 3迷殿、可以添加屬性+方法

【原因】:編譯好的實例變量存儲的位置是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这橙,在運行時,底層調(diào)用的是_objc_msgSendSuper2【重點5寂G!】

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

代碼調(diào)試

  • LGTeacher中的init方法中打印這兩種class調(diào)用

    image

    運行程序鹰晨,打印結(jié)果如下
    image

  • 進入[self class]中的class源碼

- (Class)class {
    return object_getClass(self);
}

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

其底層是獲取對象的isa,當前的對象是LGTeacher止毕,其isa是同名的LGTeacher模蜡,所以[self class]打印的是LGTeacher

  • [super class]中,其中super 是語法的 關(guān)鍵字扁凛,可以通過clangsuper的本質(zhì)忍疾,這是編譯時的底層源碼,其中第一個參數(shù)是消息接收者谨朝,是一個__rw_objc_super結(jié)構(gòu)
    image
    • 底層源碼中搜索__rw_objc_super卤妒,是一個中間結(jié)構(gòu)體
      image
    • objc中搜索objc_msgSendSuper,查看其隱藏參數(shù)
      image
    • 搜索struct objc_super
      image

      通過clang的底層編譯代碼可知字币,當前消息的接收者 等于 self则披,而self 等于 LGTeacher,所以 [super class]進入class方法源碼后洗出,其中的self是init后的實例對象士复,實例對象的isa指向的是本類,即消息接收者是LGTeacher本類
  • 我們再來看[super class]在運行時是否如上一步的底層編碼所示翩活,是objc_msgSendSuper判没,打開匯編調(diào)試蜓萄,調(diào)試結(jié)果如下
    image
    • 搜索objc_msgSendSuper2,從注釋得知澄峰,是從 類開始查找嫉沽,而不是父類
      image
    • 查看objc_msgSendSuper2的匯編源碼,是從superclass中的cache中查找方法
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame

ldp p0, p16, [x0]       // p0 = real receiver, p16 = class 取出receiver 和 class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2//cache中查找--快速查找

END_ENTRY _objc_msgSendSuper2

完整回答

所以俏竞,最完整的回答如下

  • [self class]方法調(diào)用的本質(zhì)是 發(fā)送消息绸硕,調(diào)用class的消息流程,拿到元類的類型魂毁,在這里是因為類已經(jīng)加載到內(nèi)存玻佩,所以在讀取時是一個字符串類型,這個字符串類型是在map_imagesreadClass時已經(jīng)加入表中席楚,所以打印為LGTeacher

  • [super class]打印的是LGTeacher咬崔,原因是當前的super是一個關(guān)鍵字,在這里只調(diào)用objc_msgSendSuper2烦秩,其實他的消息接收者和[self class]是一模一樣的垮斯,所以返回的是LGTeacher

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

Class cls = [LGPerson class];
void  *kc = &cls;  //
[(__bridge id)kc saySomething];

LGPerson中有一個屬性 kc_name 和一個實例方法saySomething,通過上面代碼這種方式只祠,能否調(diào)用實例方法兜蠕?為什么?

代碼調(diào)試

  • 我們在日常開發(fā)中的調(diào)用方式是下面這種
LGPerson *person = [LGPerson alloc];
[person saySomething];
  • 通過運行發(fā)現(xiàn)抛寝,是可以執(zhí)行的熊杨,打印結(jié)果如下


    image
  • [person saySomething]的本質(zhì)是對象發(fā)送消息,那么當前的person是什么盗舰?

    • personisa指向類LGPersonperson的首地址 指向 LGPerson的首地址晶府,我們可以通過LGPerson的內(nèi)存平移找到cache,在cache中查找方法
      image
  • [(__bridge id)kc saySomething]中的kc是來自于LGPerson 這個類钻趋,然后有一個指針kc郊霎,將其指向LGPerson的首地址

    image

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

image

修改: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];
  • 查看這兩種調(diào)用方式的打印結(jié)果进倍,如下所示
    • kc方式的調(diào)用打印的kc_name<ViewController: 0x7fe29170b560>

    • person方式的調(diào)用打印的kc_name(null)

      image

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

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

    image

  • 【方式一】其中kc指針中沒有任何,所以kc表示8字節(jié)指針垂蜗,self.kc_name的獲取楷扬,相當于 kc首地址的指針也需要平移8字節(jié)找kc_name解幽,那么此時的kc的指針地址是多少?平移8字節(jié)獲取的是什么烘苹?

    • kc是一個指針躲株,是存在中的,棧是一個先進后出的結(jié)構(gòu)镣衡,參數(shù)傳入就是一個不斷壓棧的過程霜定,
      • 其中隱藏參數(shù)會壓入棧,且每個函數(shù)都會有兩個隱藏參數(shù)(id self廊鸥,sel _cmd),可以通過clang查看底層編譯

      • 隱藏參數(shù)壓棧的過程望浩,其地址是遞減的,而棧是從高地址->低地址 分配的惰说,即在棧中磨德,參數(shù)會從前往后一直壓

      • super通過clang查看底層的編譯,是objc_msgSendSuper吆视,其第一個參數(shù)是一個結(jié)構(gòu)體__rw_objc_super(self典挑,class_getSuperclass),那么結(jié)構(gòu)體中的屬性是如何壓棧的揩环?可以通過自定義一個結(jié)構(gòu)體搔弄,判斷結(jié)構(gòu)體內(nèi)部成員的壓棧情況

        • p &person3
        • p *(NSNumber **)0x00007ffee83a8090
        • p *(NSNumber **)0x00007ffee83a8098
          image

          所以圖中可以得出 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方法的兩個隱藏參數(shù)浑玛,是高地址->低地址正向壓棧

    • class_getSuperClassselfobjc_msgSendSuper2中的結(jié)構(gòu)體成員,是從最后一個成員變量噩咪,即低地址->高地址反向壓棧

可以通過下面這段代碼打印下棧的存儲是否如上面所說

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

運行結(jié)果如下

image

其中為什么class_getSuperclassViewController顾彰,因為objc_msgSendSuper2返回的是當前類,兩個self胃碾,并不是同一個self涨享,而是棧的指針不同,但是指向同一片內(nèi)存空間

  • [(__bridge id)kc saySomething]調(diào)用時仆百,此時的kc是 LGPerson: 0x7ffeec381098厕隧,所以saySomething方法中傳入的self 還是LGPerson,但并不是我們通常認為的LGPerson,使我們當前傳入的消息接收者吁讨,即LGPerson: 0x7ffeec381098髓迎,是LGPerson的實例對象,此時的操作與普通的LGPerson是一致的建丧,即LGPerson的地址內(nèi)存平移8字節(jié)
    • 普通person流程:person -> kc_name - 內(nèi)存平移8字節(jié)

    • kc流程:0x7ffeec381098 + 0x80 -> 0x7ffeec3810a0,即為self排龄,指向<ViewController: 0x7fac45514f50>,如下圖所示

      image

其中 personLGPerson的關(guān)系是 person是以LGPerson為模板的實例化對象茶鹃,即alloc有一個指針地址涣雕,指向isa,isa指向LGPerson闭翩,它們之間關(guān)聯(lián)是有一個isa指向挣郭,

而kc也是指向LGPerson的關(guān)系,編譯器會認為 kc也是LGPerson的一個實例化對象疗韵,即kc相當于isa兑障,即首地址,指向LGPerson蕉汪,具有和person一樣的效果流译,簡單來說,我們已經(jīng)完全將編譯器騙過了者疤,即kc也有kc_name福澡。由于person查找kc_name是通過內(nèi)存平移8字節(jié),所以kc也是通過內(nèi)存平移8字節(jié)去查找kc_name

哪些東西在棧里 哪些在堆里

  • alloc的對象 都在

  • 指針驹马、對象中革砸,例如person指向的空間中,person所在的空間在棧中

  • 臨時變量

  • 屬性值糯累,屬性隨對象是在

注意:

  • 是從小到大算利,即低地址->高地址

  • 棧是從大到小,即從高地址->低地址分配

    • 函數(shù)隱藏參數(shù)會從前往后一直壓泳姐,即 從高地址->低地址 開始入棧效拭,

    • 結(jié)構(gòu)體內(nèi)部的成員是從低地址->高地址

  • 一般情況下,內(nèi)存地址有如下規(guī)則

    • 0x60 開頭表示在

    • 0x70 開頭的地址表示在

    • 0x10 開頭的地址表示在全局區(qū)域

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

  • 1缎患、通過SideTable 找到我們的 weak_table

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

  • 3阎肝、然后append_referrer(entry挤渔,referrer)將我的新弱引用的對象加進去entry

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

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


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俯邓,更是在濱河造成了極大的恐慌骡楼,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稽鞭,死亡現(xiàn)場離奇詭異鸟整,居然都是意外死亡,警方通過查閱死者的電腦和手機朦蕴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門篮条,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吩抓,你說我怎么就攤上這事涉茧。” “怎么了疹娶?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵伴栓,是天一觀的道長。 經(jīng)常有香客問我雨饺,道長钳垮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任额港,我火速辦了婚禮饺窿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘移斩。我一直安慰自己肚医,他們只是感情好,可當我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布叹哭。 她就那樣靜靜地躺著忍宋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪风罩。 梳的紋絲不亂的頭發(fā)上糠排,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音超升,去河邊找鬼入宦。 笑死,一個胖子當著我的面吹牛室琢,可吹牛的內(nèi)容都是我干的乾闰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼盈滴,長吁一口氣:“原來是場噩夢啊……” “哼涯肩!你這毒婦竟也來了轿钠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤病苗,失蹤者是張志新(化名)和其女友劉穎突颊,沒想到半個月后篮绿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塑崖,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡侵俗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咬展。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泽裳。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖破婆,靈堂內(nèi)的尸體忽然破棺而出涮总,到底是詐尸還是另有隱情,我是刑警寧澤荠割,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布妹卿,位于F島的核電站,受9級特大地震影響蔑鹦,放射性物質(zhì)發(fā)生泄漏夺克。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一嚎朽、第九天 我趴在偏房一處隱蔽的房頂上張望铺纽。 院中可真熱鬧,春花似錦哟忍、人聲如沸狡门。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽其馏。三九已至,卻和暖如春爆安,著一層夾襖步出監(jiān)牢的瞬間叛复,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工扔仓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留褐奥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓翘簇,卻偏偏與公主長得像撬码,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子版保,可洞房花燭夜當晚...
    茶點故事閱讀 43,435評論 2 348