Block是C級語法和運行時特性。它們相似於標準C函數,可是除了可執行代碼以外,它們還可能包含對自動(堆棧)或託管(堆)內存的變量綁定。所以,Block能夠維護一組狀態(數據),它能夠用來在執行時影響行爲。c++
您可使用Blocks來組合函數表達式,這些表達式能夠被傳遞給API,可選地存儲,並由多個線程使用。Block對於回調來講特別有用,由於塊包含在回調上執行的代碼和執行過程當中須要的數據。app
能夠在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++)一塊兒工做。語法反映了這個目標。iphone
__block
變量加了它爲啥就能被修改了?不少人也許會說是傳入了地址,這種說法很片面的,若是是這樣的其實徹底不用加__block
,蘋果幫你作了就好了呀。其實__block以後翻譯成C語言源碼以後,變量會變成一個對象,該對象存儲了原來變量的地址,函數傳入的是一個對象。經過LLVM的編譯器-rt子項目存儲庫中找到runtime下的Block源碼svn
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的對象spa
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編譯器給咱們作了啥
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"的說法是很片面的吧啦