Block詳解一(底層分析)
- 2020 年 3 月 17 日
- 筆記
本篇部落格不再講述Block的基本定義使用,最近而是看了很多的block部落格講述的太亂太雜,所以抽出時間整理下block的相關底層知識,在講述之前,提出幾個問題,如果都可以回答出來以及知道原理,大神繞過,反之,希望本篇部落格對大家面試或者block不熟悉者有所幫助,以後會不斷更新部落格,歡迎關注和指正!!!
- blcok的原理是怎樣的?本質又是什麼?
- __block的作用是什麼?有什麼使用注意點?
- block的屬性修飾詞為什麼是copy修飾?使用block有哪些使用注意事項?
- block在修改NSMutableArray,需要不需要添加__block?
一、 block本質
- blcok本質是OC對象,它內部也有個isa指針,在OC中有isa指針的對象,可以認定為OC對象
- block對象是封裝了函數調用以及函數調用環境的OC對象
通過下面例子看下block結構
文件結構
int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; void (^block)(int, int) = ^(int a, int b){ NSLog(@"This is a block --%d",age); NSLog(@"This is a block"); NSLog(@"This is a block"); }; block(10, 20); } return 0; }
通過clang編譯器將OC程式碼編譯成C語言程式碼,並生成了在後綴名為.cpp的C++文件中,clang命令為
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
然後查看編譯出來的c++ main.cpp文件,和main.m同一個地方,將它移入到項目中,並在Build Phases->Compile Sources中刪除main.cpp,然後就可以編譯成功
這次開始查看main.cpp的,對比兩個文件,找出main.cpp的對應的main函數
看到上面main.cpp左邊的調用block,10,20前面有很多的強制類型轉換,最後可以是funcPtr (block,10,20)在.cpp中海油一個__main_block_imp_0地址,查看其地址內容
二、block變數捕獲機制
舉例1: block變數捕獲-auto變數
經常書寫int age = 10,前面都是有默認關鍵字auto的(也可以不書寫,經常這樣的),下面的結果是什麼?
int age = 10 等價於 auto int age = 10 (auto自動變數,離開作用域就會銷毀)
int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; void (^block)(void) = ^{ NSLog(@"age is %d", age); }; age = 20; block(); } return 0; }
猜一下運行結果
再次運行查看編譯出來的main.cpp,重複上面的步驟,對比main.cpp
查看__main_block_impl_0的結構,傳入的參數age = 10,block內部新增了一個變數用於存儲age
block的內部的age = 10 ,並不會改變,所以列印結果為10(當創建block的內容,age = 10已經存在了block中,並不會隨外部改變而改變)
舉例2:block變數捕獲-static變數
int main(int argc, const char * argv[]) { @autoreleasepool { auto int age = 10; static int height = 10; void (^block)(void) = ^{ NSLog(@"age is %d, height is %d", age, height); }; age = 20; height = 20; block(); } return 0; }
結果為age = 10,height = 20
將其編譯為cpp,對比下
查看__main_block_impl_0的程式碼結構
發現block捕獲到了age和height,所以block會捕獲到局部變數,而靜態局部變數block存放的是地址,所以未來修改height的值時,取出的是所指向的最新的height值
舉例3:block變數捕獲-全局變數
int age = 10; static int height = 10; int main(int argc, const char * argv[]) { @autoreleasepool { void (^block)(void) = ^{ NSLog(@"age is %d, height is %d", age, height); }; age = 20; height = 20; block(); } return 0; }
查看.cpp文件有沒有捕獲到全局變數?–直接訪問
對於上面的block變數捕捉機制,總結如下:
三、block的類型
block的底層結構如下
block有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
到底什麼類型的block屬於__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__?
先不鋪墊那麼多,直接給出結論:
下面來一一驗證結論
就是像上面提問那樣,按總結的訪問了auto變數應該是NSStackBlock,怎麼成為了NSMallocBlock了呢?
這是因為編譯器默認在ARC環境下,應該切換到MRC環境下,看一下真正的block類型,至於ARC的,下篇講述!!!
將編譯器改為去除ARC,在Build Settings -> automatic Reference Counting 中將ARC改為MRC
改成MRC後再次允許查看結果
那什麼時候是NSMallocBlock呢?
NSStackBlock調用copy變為NSMallocBlock,但是NSGlobalBlock調用了copy依然是NSGlobalBlock,NSMallocBlock調用了copy方法引用計數會+1
那麼在ARC環境下什麼時候block會調用copy呢?(從棧空間->堆空間)
在ARC環境下,編譯器會根據情況自動將棧上的block複製到堆上,比如有以下情況:
- block作為函數返回值時
- block賦值給__strong指針時
- block作為Cocoa API中方法名有usingBlock的方法參數時 如:[array enumeratorObjectsUsingBlock]
- block作為GCD API的方法參數時
四、對象類型的auto變數
上面講述auto變數是Int等基本類型,現在改成對象類型,如Person類對象[ARC環境下面]
1 typedef void(^ZXYBlock)(void); 2 int main(int argc, const char * argv[]) { 3 @autoreleasepool { 4 ZXYBlock block ; 5 { 6 Person *person = [[Person alloc]init]; 7 person.age = 10; 8 block = ^{ 9 NSLog(@"---------%d", person.age); 10 }; 11 } 12 NSLog(@"block執行完"); 13 } 14 return 0; 15 } 16 17 @interface Person : NSObject 18 @property (nonatomic, assign) int age; 19 @end 20 21 @implementation Person 22 -(void)dealloc { 23 NSLog(@"person對象已釋放"); 24 } 25 @end
將上面的程式碼打breakPoint斷點在12行處,查看Person對象是否在打括弧{}內釋放
發現在列印之前並沒有釋放person對象,猜想block引用了person,導致block執行完之後才被釋放(block當autoReleasePool執行完之後才會被釋放) 查看c++程式碼
查看main函數調用
通過上面查看結構體struct __main_block_desc 裡面多了兩個copy和dispose(相當於retain)對person進行捕捉到age變數,當block不被釋放,person對象也不會被釋放
當斷點改到14行,執行完block時,查看結果
block被釋放,完成列印釋放
總結:對象訪問的auto變數
當block內部訪問了對象類型的auto變數時
- 如果block在棧上,將不會對auto變數產生強引用
- 如果block被拷貝到堆上
- 會調用block內部的copy函數
- copy函數內部會調用Block_object_assign 函數
-
Block_object_assign函數會根據auto變數的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
- 如果block從堆上移除
- 會調用block內部的dispose函數
- dispose函數內部會調用_Block_object_dispose函數
- _Block_object_dispose函數會自動釋放引用的auto變數
以上就是block詳解一的內容,下一篇講述block剩下的知識點,歡迎關注!!!