iOS经典面试题之深入分析block相关高频面试题
生活随笔
收集整理的這篇文章主要介紹了
iOS经典面试题之深入分析block相关高频面试题
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
一、前言
- 本文重點(diǎn)來(lái)研究一下 objc 的 block,并具體來(lái)分析一下以下一些面試題目:
-
- block 的內(nèi)部實(shí)現(xiàn),結(jié)構(gòu)體是什么樣?
-
- block 是類嗎?有哪些類型?
-
- 一個(gè) int 變量被 __block 修飾與否的區(qū)別?block 的變量如何截獲?
-
- block 在修改 NSMutableArray,需不需要添加 __block?
-
- block 怎么進(jìn)行內(nèi)存管理?
-
- block 可以用 strong 修飾嗎?
-
- 解決循環(huán)引用時(shí),為什么要用 __strong、__weak 修飾?
-
- block 發(fā)生 copy 時(shí)機(jī)是什么?
-
- block 訪問(wèn)對(duì)象類型的 auto 變量時(shí),在 ARC 和 MRC 下有什么區(qū)別?
- 在回答這些問(wèn)題之前,需要了解一些 block 背景相關(guān)的知識(shí),如下:
-
- 如何查看 block 的內(nèi)部實(shí)現(xiàn),也就是說(shuō)轉(zhuǎn)換成背后真正的 c/c++ 代碼的 block 是什么樣的?
-
- block 的轉(zhuǎn)換格式是什么?
-
- block 原理是什么?
-
- 關(guān)于 block 變量的作用域是什么?
二、Objective-C 轉(zhuǎn) C++ 的方法
- 現(xiàn)有一個(gè) TestClass.m 類,其中 block 代碼如下:
- 打開終端,cd 到 TestClass.m 所在目錄,使用如下命令:
- 就會(huì)在當(dāng)前文件夾內(nèi)自動(dòng)生成對(duì)應(yīng)的 TestClass.cpp 文件。如果提示 clang 沒(méi)有的話,則需要安裝,輸入如下命令:
- 經(jīng)過(guò)上述轉(zhuǎn)換操作,可以在 TestClass.cpp 中最下面發(fā)現(xiàn)如下 C++ 代碼:
- 通過(guò)上述代碼,可以發(fā)現(xiàn) block 其實(shí)是一個(gè)結(jié)構(gòu)體類型,底層實(shí)現(xiàn)會(huì)根據(jù) __類名__方法名_block_impl_下標(biāo) (0 代表這個(gè)方法或者這個(gè)類中第 0 個(gè) block):
三、關(guān)于變量的作用域
- c 語(yǔ)言的函數(shù)中可能使用的參數(shù)變量種類:
-
- 參數(shù)類型
-
- 自動(dòng)變量(局部變量)
-
- 靜態(tài)變量(靜態(tài)局部變量)
-
- 靜態(tài)全局變量
-
- 全局變量
- 由于存儲(chǔ)區(qū)域特殊,這其中有三種變量是可以在任何時(shí)候以任何狀態(tài)調(diào)用的:
-
- 靜態(tài)變量
-
- 靜態(tài)全局變量
-
- 全局變量
- 而其它兩種,則是有各自相應(yīng)的作用域,超過(guò)作用域后,會(huì)被銷毀。
四、block 的內(nèi)部實(shí)現(xiàn),結(jié)構(gòu)體是什么樣子?
- block 的內(nèi)部實(shí)現(xiàn)如下:
- 可以看得出來(lái) __TestClass__testMethods_block_impl_0 有 3 個(gè)部分組成:
-
- impl 函數(shù)指針指向 __TestClass__testMethods_block_impl_0:
-
- Desc 指向 __TestClass__testMethods_block_impl_0的Desc 指針,用于描述當(dāng)前這個(gè) block 的附加信息,包括結(jié)構(gòu)體的大小等信息:
-
- __TestClass__testMethods_block_impl_0() 構(gòu)造函數(shù),也就是該 block 的具體實(shí)現(xiàn):
- 此結(jié)構(gòu)體中,isa 指針保持這所屬類的結(jié)構(gòu)體的實(shí)例的指針,struct __TestClass__testMethods_block_impl_0 相當(dāng)于 Objective-C 類對(duì)象的結(jié)構(gòu)體,_NSConcreteStackBlock 相當(dāng)于 block 的結(jié)構(gòu)體實(shí)例,也就是說(shuō) block 其實(shí)就是 Objective-C 對(duì)于閉包的對(duì)象實(shí)現(xiàn)。
五、block 是類嗎?有哪些類型?
- block 也可以理解為類,因?yàn)樗?isa 指針、block.isa 的類型,包括:
-
- _NSConcreteGlobalBlock 跟全局變量一樣,設(shè)置在程序的數(shù)據(jù)區(qū)域(.data)中;
-
- _NSConcreteStackBlock 棧上(上文的都是棧上 block);
-
- _NSConcreteMallocBlock 堆上。
- block 的 isa 可以按位運(yùn)算。
六、一個(gè) int 變量被 __block 修飾與否的區(qū)別?block的變量如何截獲?
① 被 __block 修飾與否的區(qū)別
- 現(xiàn)有如下示例:
- 通過(guò) __block 修飾 int a,block 體中對(duì)這個(gè)變量的引用是指針拷貝,它會(huì)作為 block 結(jié)構(gòu)體構(gòu)造參數(shù)傳入到結(jié)構(gòu)體中且復(fù)制這個(gè)變量的指針引用,從而達(dá)到可以修改變量的作用。
- int b 沒(méi)有被 __block 修飾,block 內(nèi)部對(duì) b 是值 copy,因此在 block 內(nèi)部修改 b 而不會(huì)影響外部 b 的變化。
② block的變量如何截獲?
- 通過(guò)如下代碼,來(lái)觀察要一下變量的捕獲:
- 運(yùn)行結(jié)果:
- 把上面的代碼翻譯成 C++:
- 在 Objc 中,C 結(jié)構(gòu)體里不能含有被 __strong 修飾的變量,因?yàn)榫幾g器不知道應(yīng)該何時(shí)初始化和廢棄 C 結(jié)構(gòu)體。但是 Objc 的運(yùn)行時(shí)庫(kù)能夠準(zhǔn)確把握 block 從棧復(fù)制到堆,以及堆上的 block 被廢棄的時(shí)機(jī),實(shí)現(xiàn)上是通過(guò) __TestClass__testMethods_block_copy_0 函數(shù)和 __TestClass__testMethods_block_dispose_0 函數(shù)進(jìn)行:
-
- _Block_object_assign 相當(dāng)于 retain 操作,將對(duì)象賦值在對(duì)象類型的結(jié)構(gòu)體成員變量中;
-
- _Block_object_dispose 相當(dāng)于 release 操作。
- 這兩個(gè)函數(shù)調(diào)用的時(shí)機(jī)是在什么時(shí)候呢?
| __TestClass__testMethods_block_copy_0 | 從棧復(fù)制到堆時(shí) |
| __TestClass__testMethods_block_dispose_0 | 堆上的Block被廢棄時(shí) |
③ 什么時(shí)候棧上 block 會(huì)被復(fù)制到堆呢?
- 調(diào)用 block 的 copy 函數(shù)時(shí);
- block 作為函數(shù)返回值返回時(shí);
- 將 block 賦值給附有 __strong 修飾符 id 類型的類或者 block 類型成員變量時(shí);
- 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中傳遞 block 時(shí)。
④ block 什么時(shí)候被廢棄?
- 堆上的 block 被釋放后,誰(shuí)都不再持有 block 時(shí)調(diào)用 dispose 函數(shù);
七、block 在修改 NSMutableArray 需不需要添加 __block?
- 修改 NSMutableArray 的存儲(chǔ)內(nèi)容的話,是不需要添加 __block 修飾的。
- 修改 NSMutableArray 對(duì)象的本身,那必須添加 __block 修飾。
八、block 怎么進(jìn)行內(nèi)存管理?
- 在上文中的 block 的構(gòu)造函數(shù) __TestClass__testMethods_block_impl_0 中的 isa 指針指向的是 &_NSConcreteStackBlock,它表示當(dāng)前的 block 位于棧區(qū)中。
| _NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
| _NSConcreteStackBlock | 棧 | 從棧拷貝到堆 |
| _NSConcreteMallocBlock | 堆 | 引用計(jì)數(shù)增加 |
- 全局 block:_NSConcreteGlobalBlock 的結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)存儲(chǔ)區(qū),所以可以在程序的任意位置通過(guò)指針來(lái)訪問(wèn),它的產(chǎn)生條件:
-
- 記述全局變量的地方有 block 語(yǔ)法時(shí);
-
- block 不截獲的自動(dòng)變量。
-
- 以上兩個(gè)條件只要滿足一個(gè)就可以產(chǎn)生全局 block。
- 棧 block:_NSConcreteStackBlock 在生成 block 以后,如果這個(gè) block 不是全局 block,那它就是棧 block,生命周期在其所屬的變量作用域內(nèi)(也就是說(shuō)如果銷毀取決于所屬的變量作用域)。如果 block 變量和 __block 變量復(fù)制到了堆上以后,則不再會(huì)受到變量作用域結(jié)束的影響,因?yàn)樗兂闪硕?block。
- 堆 block:_NSConcreteMallocBlock 將棧 block 復(fù)制到堆以后,block 結(jié)構(gòu)體的 isa 成員變量變成_NSConcreteMallocBlock。
九、block 可以用 strong 修飾嗎?
- 在 ARC 中可以,因?yàn)樵?ARC 環(huán)境中的 block 只能在堆內(nèi)存或全局內(nèi)存中,因此不涉及到從棧拷貝到堆中的操作。
- 在 MRC 中不行,因?yàn)橐锌截愡^(guò)程,如果執(zhí)行 copy 用 strong 的話會(huì) crash,strong 是 ARC 中引入的關(guān)鍵字,如果使用 retain 相當(dāng)于忽視了 block 的 copy 過(guò)程。
十、解決循環(huán)引用時(shí),為什么要用 __strong、__weak 修飾 block?
- 首先 block 捕獲變量的時(shí)候,結(jié)構(gòu)體構(gòu)造傳入了self,造成了默認(rèn)的引用關(guān)系,因此一般在 block 外部對(duì)操作對(duì)象會(huì)加上 __weak;
- 在 block 內(nèi)部使用 __strong 修飾符的對(duì)象類型的自動(dòng)變量,那么當(dāng) block 從棧復(fù)制到堆的時(shí)候,該對(duì)象就會(huì)被 block 所持有,但是持有的是上面加了 __weak,所以形成了彼消此漲的鏈條,剛好能解決 block 延遲銷毀的時(shí)候?qū)ν獠繉?duì)象生命周期造成的影響,如果不這樣做很容易造成循環(huán)引用。
十一、block 發(fā)生 copy 時(shí)機(jī)?
- 在 ARC 中,編譯器將創(chuàng)建在棧中 block 會(huì)自動(dòng)拷貝到堆內(nèi)存中,而 block 作為方法或函數(shù)的參數(shù)傳遞時(shí),編譯器不會(huì)做 copy 操作。
-
- 調(diào)用 block 的 copy 函數(shù)時(shí);
-
- block 作為函數(shù)返回值返回時(shí);
-
- 將 block 賦值給附有 __strong 修飾符 id 類型的類或者 block 類型成員變量時(shí);
-
- 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中傳遞 block 時(shí)。
十二、block 訪問(wèn)對(duì)象類型的 auto 變量時(shí),在 ARC 和 MRC 下有什么區(qū)別?
- 在 ARC 下,棧區(qū)創(chuàng)建的 block 會(huì)自動(dòng) copy 到堆區(qū);而 MRC 下,就不會(huì)自動(dòng)拷貝,需要手動(dòng)調(diào)用 copy 函數(shù)。
- block 的 copy 操作,當(dāng) block 從棧區(qū) copy 到堆區(qū)的過(guò)程中,也會(huì)對(duì) block 內(nèi)部訪問(wèn)的外部變量進(jìn)行處理,它會(huì)調(diào)用 block_object_assign 函數(shù)對(duì)變量進(jìn)行處理,根據(jù)外部變量是 strong 還會(huì) weak 對(duì) block 內(nèi)部捕獲的變量進(jìn)行引用計(jì)數(shù) +1 或 -1,從而達(dá)到強(qiáng)引用或弱引用的作用。
- 因此在 ARC 下,由于 block 被自動(dòng) copy 到了堆區(qū),從而對(duì)外部的對(duì)象進(jìn)行強(qiáng)引用,如果這個(gè)對(duì)象同樣強(qiáng)引用這個(gè) block,就會(huì)形成循環(huán)引用。
- 在 MRC 下,由于訪問(wèn)的外部變量是 auto 修飾,所以這個(gè) block 屬于棧區(qū)的,如果不對(duì) block 手動(dòng)進(jìn)行 copy 操作,在運(yùn)行完 block 的定義代碼段后,block 就會(huì)被釋放,而由于沒(méi)有進(jìn)行 copy 操作,所以這個(gè)變量也不會(huì)經(jīng)過(guò) block_object_assign 處理,也就不會(huì)對(duì)變量強(qiáng)引用。
- 簡(jiǎn)而言之,ARC 下會(huì)對(duì)這個(gè)對(duì)象強(qiáng)引用,而 MRC 下則不會(huì)。
總結(jié)
以上是生活随笔為你收集整理的iOS经典面试题之深入分析block相关高频面试题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Swift之深入解析Xcode13对Sw
- 下一篇: 【网络通信与信息安全】之深入解析TCP连