Block詳解一(底層分析)
本篇部落格不再講述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剩下的知識點,歡迎關