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
修飾符,系統會自動建立相應的結構體,佔用不必要的記憶體空間