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