iOS block原理詳解
- block本質
-
block底層就是一個struct __main_block_impl_0型別的結構體,這個結構體中包含一個isa指標,本質上是一個OC物件 -
block是封裝了函式呼叫以及函式呼叫環境的OC物件
-
block底層結構
block底層結構就是__main_block_impl_0結構體,內部包含了impl結構體和Desc結構體以及外部需要訪問的變數,block將需要執行的程式碼放到一個函式裡,impl內部的FuncPtr指向這個函式的地址,通過地址呼叫這個函式,就可以執行block裡面的程式碼了。Desc用來描述block,內部的reserved作保留,Block_size描述block佔用記憶體 -
block的變數捕獲
區域性變數block訪問方式是值傳遞,auto自動變數可能會銷燬,記憶體可能會消失,不採用指標訪問;區域性靜態變數block訪問方式是指標傳遞,static變數一直儲存在記憶體中,指標訪問即可;全域性變數、靜態全域性變數block不需要對變數捕獲,直接取值
// block的變數捕獲程式碼解析如下
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();
-------------------------------------------------
output: age is 10,height is 20
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 值傳遞
int *height; // 指標傳遞
}
複製程式碼
- block的型別
| block型別 | 環境 | 儲存域 | copy操作後 |
|---|---|---|---|
__NSGlobalBlock__ |
沒有訪問auto變數
|
資料區 | 什麼也不做,型別不改變 |
__NSStackBlock__ |
訪問了auto變數
|
棧區 | 從棧複製到堆,型別改變為__NSMallocBlock__
|
__NSMallocBlock__ |
__NSStackBlock__呼叫了copy
|
堆區 | 引用計數+1,型別不改變 |
- 在
ARC環境下,編譯器會根據以下幾種情況自動將棧上的block複製到堆上: 1、block作為函式返回值時,比如使用=2、將block賦值給__strong指標時 3、block作為Cocoa API中方法名含有usingBlock的方法引數時 4、block作為GCD API的方法引數時
- 物件型別的auto變數
- 當
block內部訪問了物件型別的auto變數時: 如果block在棧空間,不論是ARC還是MRC環境,不管外部變數是強引用還是弱引用,block都會弱引用訪問物件 如果block在堆空間,如果外部強引用,block內部也是強引用;如果外部弱引用,block內部也是弱引用 - 棧block:
a) 如果
block是在棧上,將不會對auto變數產生強引用b)棧上的block隨時會被銷燬,也沒必要去強引用其他物件 - 堆block:
1、如果block被拷貝到堆上
a) 會呼叫
block內部的copy函式 b)copy函式內部會呼叫_Block_object_assign函式 c)_Block_object_assign函式會根據auto變數的修飾符__strong、__weak、__unsafe_unretained做出相應的操作,形成強引用或者弱引用2、如果block從堆上移除 a) 會呼叫block內部的dispose函式 b)dispose函式內部會呼叫_Block_object_dispose函式 c)_Block_object_dispose函式會自動釋放引用的auto變數(release,引用計數-1,若為0,則銷燬)
- __block
-
__block修飾符作用:__block可以用於解決block內部無法修改auto變數值的問題__block不能修飾全域性變數、靜態變數static -
__block修飾符原理: 編譯器會將__block變數包裝成一個結構體__Block_byref_age_0,結構體內部*__forwarding是指向自身的指標,內部還儲存著外部auto變數的值__block的forwarding指標如下圖:
棧上,__block結構體中的__forwarding指標指向自己,一旦複製到堆上,棧上的__block結構體中的__forwarding指標會指向堆上的__block結構體,堆上__block結構體中的__forwarding還是指向自己。假設age是棧上的變數,age->__forwarding會拿到堆上的__block結構體,age->__forwarding->age會把20賦值到堆上,不論是棧上還是堆上的__block結構體,都能保證20賦值到堆的結構體裡
- 思考題:block修改NSMutableString、NSMutableArray、NSMutableDictionary,需不需要新增__block 題目如下:以下程式碼是否可以正確執行
int main(int argc,const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
[array addObject: @"5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();
}
return 0;
}
複製程式碼
分析:可以正確執行,因為在block塊中僅僅是使用了array的記憶體地址,往記憶體地址中新增內容,並沒有修改arry的記憶體地址,因此array不需要使用__block修飾也可以正確編譯。當僅僅是使用區域性變數的記憶體地址,而不是修改的時候,儘量不要新增__block,通過上述分析我們知道一旦添加了__block修飾符,系統會自動建立相應的結構體,佔用不必要的記憶體空間
