日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS经典面试题之深入分析block相关高频面试题

發布時間:2024/5/21 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 代碼如下:
@interface TestClass () @end@implementation TestClass - (void)testMethods {void (^blockA)(int a) = ^(int a) {NSLog(@"%d",a);};if (blockA) {blockA(1990);} } @end
  • 打開終端,cd 到 TestClass.m 所在目錄,使用如下命令:
clang -rewrite-objc TestClass.m
  • 就會在當前文件夾內自動生成對應的 TestClass.cpp 文件。如果提示 clang 沒有的話,則需要安裝,輸入如下命令:
brew install clang-format或者brew link clang-forma然后輸入 下面命令 測試是否好使clang-format --help
  • 經過上述轉換操作,可以在 TestClass.cpp 中最下面發現如下 C++ 代碼:
// @interface TestClass () /* @end */// @implementation TestClassstruct __TestClass__testMethods_block_impl_0 {struct __block_impl impl;struct __TestClass__testMethods_block_desc_0* Desc;__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_ee18d3_mi_0,a);}static struct __TestClass__testMethods_block_desc_0 {size_t reserved;size_t Block_size; } __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));if (blockA) {((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);} }
  • 通過上述代碼,可以發現 block 其實是一個結構體類型,底層實現會根據 __類名__方法名_block_impl_下標 (0 代表這個方法或者這個類中第 0 個 block):
struct __類名__方法名_block_impl_下標

三、關于變量的作用域

  • c 語言的函數中可能使用的參數變量種類:
    • 參數類型
    • 自動變量(局部變量)
    • 靜態變量(靜態局部變量)
    • 靜態全局變量
    • 全局變量
  • 由于存儲區域特殊,這其中有三種變量是可以在任何時候以任何狀態調用的:
    • 靜態變量
    • 靜態全局變量
    • 全局變量
  • 而其它兩種,則是有各自相應的作用域,超過作用域后,會被銷毀。

四、block 的內部實現,結構體是什么樣子?

  • block 的內部實現如下:
struct __TestClass__testMethods_block_impl_0 {struct __block_impl impl; // 成員變量struct __TestClass__testMethods_block_desc_0* Desc; // desc 結構體聲明// 構造函數// fp 函數指針// desc 靜態全局變量初始化的 __main_block_desc_ 結構體實例指針// flags block 的負載信息(引用計數和類型信息),按位存儲.__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} }; // 將來被調用的block內部的代碼:block值被轉換為C的函數代碼 // 這里,*__cself 是指向Block的值的指針,也就相當于是Block的值它自己(相當于C++里的this,OC里的self) // __cself 是指向__TestClass__testMethods_block_impl_0結構體實現的指針 // Block結構體就是__TestClass__testMethods_block_impl_0結構體.Block的值就是通過__TestClass__testMethods_block_impl_0構造出來的 static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_9f58f7_mi_0,a); }static struct __TestClass__testMethods_block_desc_0 {size_t reserved;size_t Block_size; } __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));if (blockA) {((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);} }
  • 可以看得出來 __TestClass__testMethods_block_impl_0 有 3 個部分組成:
    • impl 函數指針指向 __TestClass__testMethods_block_impl_0:
struct __block_impl {void *isa;int Flags;int Reserved; // 今后版本升級所需的區域void *FuncPtr; // 函數指針};
    • Desc 指向 __TestClass__testMethods_block_impl_0的Desc 指針,用于描述當前這個 block 的附加信息,包括結構體的大小等信息:
static struct __TestClass__testMethods_block_desc_0 {size_t reserved; // 今后升級版本所需區域size_t Block_size; // block的大小} __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};
    • __TestClass__testMethods_block_impl_0() 構造函數,也就是該 block 的具體實現:
__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }
  • 此結構體中,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 = 10; int b = 20; PrintTwoIntBlock block = ^() { a -= 10; printf("%d, %d\n",a,b); }; block(); // 0 20 a += 20; b += 30; printf("%d, %d\n",a,b); // 20 50 block(); // 10 20
  • 通過 __block 修飾 int a,block 體中對這個變量的引用是指針拷貝,它會作為 block 結構體構造參數傳入到結構體中且復制這個變量的指針引用,從而達到可以修改變量的作用。
  • int b 沒有被 __block 修飾,block 內部對 b 是值 copy,因此在 block 內部修改 b 而不會影響外部 b 的變化。

② block的變量如何截獲?

  • 通過如下代碼,來觀察要一下變量的捕獲:
blk_t blk; { id array = [NSMutableArray new]; blk = [^(id object){ [array addObject:object]; NSLog(@"array count = %ld",[array count]); } copy]; } blk([NSObject new]); blk([NSObject new]); blk([NSObject new]);
  • 運行結果:
block_demo[28963:1629127] array count = 1 block_demo[28963:1629127] array count = 2 block_demo[28963:1629127] array count = 3
  • 把上面的代碼翻譯成 C++:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id array; // 截獲的對象 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;} };
  • 在 Objc 中,C 結構體里不能含有被 __strong 修飾的變量,因為編譯器不知道應該何時初始化和廢棄 C 結構體。但是 Objc 的運行時庫能夠準確把握 block 從棧復制到堆,以及堆上的 block 被廢棄的時機,實現上是通過 __TestClass__testMethods_block_copy_0 函數和 __TestClass__testMethods_block_dispose_0 函數進行:
static void __TestClass__testMethods_block_copy_0(struct __TestClass__testMethods_block_impl_0*dst, struct __TestClass__testMethods_block_impl_0*src) { _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __TestClass__testMethods_block_dispose_0(struct __TestClass__testMethods_block_impl_0*src) { _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); }
    • _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 位于棧區中。
block內存操作存儲域/存儲位置copy操作的影響
_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相关高频面试题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。