介紹
Block是C級語法和執行時特性。它們類似於標準C函式,但是除了可執行程式碼之外,它們還可能包含對自動(堆疊)或託管(堆)記憶體的變數繫結。因此,Block可以維護一組狀態(資料),它可以用來在執行時影響行為。
您可以使用Blocks來組合函式表示式,這些表示式可以被傳遞給API,可選地儲存,並由多個執行緒使用。Block對於回撥來說特別有用,因為塊包含在回撥上執行的程式碼和執行過程中需要的資料。
可以在GCC和Clang中使用OS X v10.6 Xcode開發工具。您可以使用OS X v10.6和之後的模組,以及隨後的iOS 4.0。Block執行時是開源的,可以在LLVM的編譯器-rt子專案儲存庫中找到。block也被提交給C標準工作組作為N1370:蘋果對C的擴充套件,因為Objective-C和c++都是從C派生出來的,block被設計用於與所有三種語言(以及Objective-C++)一起工作。語法反映了這個目標。
概念普及
- Block本質是Objective-C的物件,不是函式指標(這個有點混淆,網上普遍都說是函式指標)
- Block型別變數本質是函式指標(如下宣告其實在編譯成C語言原始碼的時候就是一個函式指標)
- 截獲自動變數 所有的變數都會截獲嗎?比如全域性變數、靜態變數。其實截獲的只是區域性變數而已
__block
變數加了它為啥就能被修改了?很多人也許會說是傳入了地址,這種說法很片面的,如果是這樣的其實完全不用加__block
,蘋果幫你做了就行了呀。其實__block之後翻譯成C語言原始碼之後,變數會變成一個物件,該物件儲存了原來變數的地址,函式傳入的是一個物件。
探尋Block本質
通過LLVM的編譯器-rt子專案儲存庫中找到runtime下的Block原始碼
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
複製程式碼
當我們宣告一個Block的時候,編譯器其實會將block轉換成以上struct結構體。
其中isa指向的是Block具體的類。有如下6中,不過其中StackBlock
、MallocBlock
、GlobalBlock
是比較常見的
/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
複製程式碼
invoke函式指標則是對應Objective-C中程式碼的具體實現
看到這裡不知大家有沒有想到runtime中objc_object
的isa呢?
其實兩個原理是一樣的。這裡就不具體介紹objc_object
所以Block即為Objective-C的物件
Block型別變數
Block的型別變數宣告如下:
int (^blk) (int);
我們宣告一個Block型別變數並且賦值
int (^blk) (int) = ^(int count){return count+1};
複製程式碼
然後我們通過如下
xcrun -sdk iphonesimulator11.2 clang -rewrite-objc -F /Applications/Xcode\ 2.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks ViewController.m
我們可以得到以下原始碼:
//對應的Block具體struct結構體
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//Block方法的具體實現
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
return count+1;
}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
}
複製程式碼
其中int (*blk) (int)
則就是對應我們的Block型別變數,這裡就就可以看到了Block型別變數的本質了,其實就是C語言的函式指標
截獲自動變數
說截獲自動變數之前我們先看以下程式碼
int tmp = 2;
int (^blk) (int) = ^(int count){
return count+tmp;
};
tmp = 3;
int result = blk(2);
NSLog(@"%d",result);
複製程式碼
以上程式碼會列印出多少呢?5還是4?? 正確答案是4
為什麼是4呢?其實就是因為Block截獲自動變數的原因
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
int tmp;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _tmp, int flags=0) : tmp(_tmp) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
int tmp = __cself->tmp; // bound by copy
return count+tmp;
}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
int tmp = 2;
int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));
tmp = 3;
int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_b80e84_mi_0,result);
}`__ViewController__viewDidLoad_block_impl_0`
複製程式碼
通過以下程式碼可以看出,當我們在給Block型別變數賦值的時候,tmp變數同時被傳入,並且被儲存到了__ViewController__viewDidLoad_block_impl_0
的struct中。這時候其實就是截獲了自動變數,由於已經在struct類中儲存了一份,即使後邊更改,也不會影響Block截獲的值。
int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));
複製程式碼
為什麼要對區域性變數進行截獲呢?而全域性變數和靜態變數不需要截獲,並且修改的的時候也不需要加__block
呢?
主要原因就是變數的生命週期。區域性變數在程式碼塊執行結束之後就會被釋放,但是Block不一定在此時釋放。所以就會出現變數超過生命週期的現象,此時對區域性變數進行截獲,即使區域性變數被釋放,但是Block同樣還是可以正常使用的。因為全域性變數和靜態變數的釋放時間肯定不會在Block之前,所以不必對他們進行截獲。
全域性變數和靜態變數儲存在全域性資料區;區域性變數儲存在棧中
__block變數儲存域探究
不知道大家有沒有想過一個問題,為什麼需要__block呢?如果沒有__block難道就修改不了變數了嗎?
我們先看一下加了__block編譯器給我們做了啥
struct __Block_byref_tmpBlock_0 {
void *__isa;
__Block_byref_tmpBlock_0 *__forwarding;
int __flags;
int __size;
int tmpBlock;
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_tmpBlock_0 *tmpBlock; // by ref
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_tmpBlock_0 *_tmpBlock, int flags=0) : tmpBlock(_tmpBlock->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
__Block_byref_tmpBlock_0 *tmpBlock = __cself->tmpBlock; // bound by ref
(tmpBlock->__forwarding->tmpBlock) = 100;
return count+(tmpBlock->__forwarding->tmpBlock);
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->tmpBlock, (void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
__attribute__((__blocks__(byref))) __Block_byref_tmpBlock_0 tmpBlock = {(void*)0,(__Block_byref_tmpBlock_0 *)&tmpBlock, 0, sizeof(__Block_byref_tmpBlock_0), 2};
int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_tmpBlock_0 *)&tmpBlock, 570425344));
int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_a1c583_mi_0,result);
}
複製程式碼
這時候大家可以與前邊沒有加__block
的程式碼進行比較,兩者的差別在哪裡。
其實大家應該很容易發現加了__block
之後,變數形成了一個struct,這個struct中儲存了變數的值,同時還有一個__forwarding
。這個__forwarding
其實就是為什麼需要__block的關鍵。
Block從棧複製到堆的時候,__block變數也會受到影響。如下:
__block變數的配置儲存域 | Block從棧到堆時的影響 |
---|---|
棧 | 從棧複製到堆並被Block持有 |
堆 | 被Block持有 |
ARC下,Block如果是棧的話,預設會copy到堆上。此時所使用的__block變數同時也會從棧被複制到堆上如下圖
那如果Block在堆上了,我們在Block中修改了變數,怎麼讓棧上的變數也同時能正確訪問呢?這其實就是__forwarding
功勞了。
__block
變數初始化的時候__forwarding
是指向本身自己的。當__block
變數從棧複製到堆上的時候,此時會將__forwarding
的值替換為複製到目標堆上的__block
變數用結構體例項的地址。如下圖:
通過此功能,無論是在Block語法中、Block語法外使用__block
變數,還是__block
變數配置在棧上或堆上,都可以順利地訪問一個__block
變數。
到這裡大家應該明白了"__block
加了之後,是把變數的地址傳入Block"的說法是很片面的吧啦