先看一段代碼,打印結果是什么?如果將int val =3
改為__block int val =3
呢?為什么?
int val=3;
void(^block)()=^{
NSLog(@"%d",val);
};
val=5;
block();
block是什么?
很多教程資料上的解釋是“帶有自動變量值的匿名函數”。但這種解釋不利于理解。其實對于一個block來說:它更像一個微型的程序。
我們知道程序就是數據加上算法,顯然,block有著自己的數據和算法??梢钥吹健T谶@個簡單的例子中,block的數據就是int類型變量val,它的算法就是簡單的NSLog方法。對于一般的block來說,他的數據就是傳入的參數和定義這個block時截獲的變量。而它的算法就是我們往里面寫的那些方法,函數調用等。
認為block像是一個微型程序的另一個原因是block對象可以由程序員選擇在什么時候調用,比如,我可以自己選擇時機執行這個block,或者在另一個類里執行這個block。
Block是一個Objective-C的對象。
block是如何實現的?
block的定義和調用是分離的,通過clang編譯器,可以看到block和其他OC對象一樣,都是被編譯成C語言里普通的struct結構體來實現的。
源碼:
int main(){
void(^block)(void) = ^{printf("Block\n");};
block();
return0;
}
編譯后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void*FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl imply;
struct __main_block_desc_0 *Desc;
__main_block_impl_0(void*fp,struct __main_block_desc_0 *desc,int flags=0){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; Desc = desc;
}
};
struct void __main_block_func_0(struct __main_block_impl_0 *__cself){
printf("Block\n");
}
static struct__main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
代碼非常長,但是并不復雜,一共四個結構體,顯然一個block對象被編譯為了一個____main_block_impl_0__類型的結構體。這個結構體由兩個成員變量結構體和一個構造函數組成。兩個結構體分別是____block_impl__和main_block_desc_0類型的。其中____block_impl__結構體中有一個函數指針,指針將指向____mian_block_func_0__類型的結構體。關系圖如下:
block在定義的時候:
//調用____main_block_impl_0結構體的構造函數
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
block在調用的時候:
(*blk->impl.FuncPtr)(bulk);
之前說到,block有自己的數據和算法。顯然算法就是放在__main_block_func_0結構體中的。那么數據在哪里呢?這個問題比較復雜,先看一下文章最初的demo編譯成什么樣,為簡化代碼,這里只貼出需要修改的部分。
struct __block_impl imply;
struct __main_block_desc_0 *Desc;
int val;
__main_block_impl_0(void *fp,struct __main_block_desc_0 *desc,int_val,int flags=0 ) :val(_val){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct void __main_block_func_0(struct__main _block_impl_0 *__cself){
int val= __cself->val;
printf("val = %d",val);
}
可以看到,當block需要截獲自動變量的時候,首先會在____mian_block_impl_0__結構體中增加一個成員變量并在結構體的構造函數中對變量賦值,以上這些對應著block對象的定義。
在block被執行的時候,把____mian_block_impl_0__結構體,也就是blcok對象當做參數傳入__mian_block_func_0結構體中,取出其中val的值,進行接下來的操作。
為什么block中不能修改變量的值?
通過把block拆成這四個結構體,系統’完美’的實現了一個block,使得它可以結構自動變量,也可以像一個微型程序一樣在任何時刻都可以被調用。但是,block還存在一個致命的不足:
注意到之前的____mian_block_func_0__結構體,里面有printf方法,用到了val,但是這個block和最初block截獲的block,除了數值一樣,在也沒有一樣的地方了。參見這句代碼:
int val= ____cself->val;__
當然這并沒什么影響,甚至還有好處,因為 int val變量定義在棧上,在block調用時已經被銷毀,但是我們還可以正常訪問這個變量。但是試想一下,如果我希望在block中修改變量的值,那么收到影響的事int val 而非cself->val,事實上即使是__cself->val,也只是截獲的自動變量的副本,要想修改block定義之外的自動變量,是不可能的事情。
既然無法實現修改截獲的自動變量,那么編譯器干脆就禁止程序員這么做了。
__block修飾符是如何做到修改變量的?
__block int val =3;//修改后的代碼
編譯后:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl imply;
struct __main_block_desc_0 *Desc;
__Block_byref_val_0 *val;
__main_block_impl_0(void *fp,struct__main_block_desc_0 *desc,__Block_byref_val_0 *_val,intflags=0) :val(_val->__forwrding){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct void __main_block_func_0(struct__main_block_impl_0 *__cself){
__Block_byref_val_0 *val= __cself->val;
printf("val = %d",val->__forwarding->val); }
改動并不大,簡單來說,只是把val封裝在了一個結構體中而已,五個結構體之間關系如下:
關鍵在于____mian_block_impl_0__結構體中的這一行:
Block_byref_val_0 *val;
由于main_block_impl_0__結構體中現在保存了一個指針變量,所以任何對這個指針的操作,是可以影響到原來的變量的。
進一步,我們考慮截獲的自動變量是Objective-C的對象的情況。在開啟ARC的情況下,將會強引用這個對象一次。這也保證了原對象不被銷毀,但與此同時,也會導致循環引用問題。
希望對你有所幫助!