Block 的實質
Block 是“帶有自動變量值的匿名函數”,我們可以通過 Block 的實現來加深理解捕仔。首先通過 clang 工具可以將含有 Block 的源代碼轉換為一般C語言代碼匕积。
將下面代碼保存為 test.m:
int main()
{
void (^blk)(void) = ^{int i=0;};
blk();
return 0;
}
在終端執(zhí)行命令clang -rewrite-objc test.m
,生成 test.cpp C 代碼文件榜跌,我們來看下 block 語句^{int i=0;};
轉換后的代碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i=0; }
__main_block_impl_0 是一個結構體闪唆,如下:
struct __main_block_impl_0 {
struct __block_impl impl;
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;
}
};
__main_block_impl_0
有兩個成員變量:impl,Desc钓葫,還有一個構造函數__main_block_impl_0(void*, struct, int)
悄蕾。
我們先看impl
,__block_impl
類型础浮, block 結構體:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
第二個成員變量 Desc帆调,是__main_block_desc_0
類型指針:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
reserved 為預留字段,Block_size 是該 block 的大小
下面我們看看該結構體的構造函數
__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;
}
以上就是初始化 __main_block_impl_0
結構體成員的源代碼豆同。fp 是 __main_block_func_0
函數指針番刊,賦給 impl.FuncPtr,通過 impl.FuncPtr 即可調用 block 函數影锈。__block_impl
結構體的 isa
成員使用_NSConcreteStackBlock
初始化芹务,這里的_NSConcreteStackBlock
是什么蝉绷?首先要理解 Objective-C 類和對象的實質。其實枣抱,所謂 Block 就是 Objective-C 對象熔吗。在 /usr/include/objc/runtime.h 中 isa 的聲明:
typedef struct objc_object {
Class isa;
} *id;
isa 保持該類的結構體實例指針。_NSConcreteStackBlock 是 Block 結構體存儲類型佳晶。
Block 存儲區(qū)
- _NSConcreteStackBlock:存儲在棧上桅狠,超出作用域被銷毀。
- _NSConcreteGlobalBlock:存儲在程序的數據區(qū)域中轿秧。當 block 定義在全區(qū)或者 block 沒有截獲外部變量時中跌,block 存在數據區(qū)。
- _NSConcreteMallocBlock:存儲在由 malloc 函數分配的堆中淤刃。使用此種方式晒他,可在 block 作用域外訪問 block,引用計數為 0 時被銷毀逸贾。
截獲自動變量
int main()
{
int val = 3;
void (^blk)(void) = ^{int i = val;};
blk();
return 0;
}
那么 __main_block_impl_0
結構體變成了
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
}
多了一個成員變量 val陨仅,val 被截獲了。所謂的截獲自動變量值铝侵,就是 block 所使用的外部變量被保存到 Block 的結構體實例中灼伤。
__block
block 不能修改外部變量,但如果在聲明變量的時候加上關鍵字 __block 就可在 block 內部修改外部變量咪鲜。這是如何實現的狐赡?看代碼:
int main()
{
__block int val = 3;
void (^blk)(void) = ^{val = 1;};
blk();
return 0;
}
轉換后的代碼多了一個結構體聲明
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
val 被轉換成一個結構體,其中__forwarding 是一個指向自身的指針疟丙,當 block 被拷貝時颖侄,該指針指向堆上的結構體實例。而__main_block_impl_0
val 變成對這個結構體的引用:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val;
}
因此可以在 block 內部修改外部的變量享郊。
同時览祖,改變的還有__main_block_desc_0
,多了兩個成員變量 copy 和 dispose炊琉,在拷貝 block 的時候使用到展蒂。
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
變量作用域結束時,棧上的 __block
變量和 Block 也被廢棄苔咪,復制到堆上的 __block
變量和 Block 在變量作用域結束時不受影響锰悼。
__block
用結構體成員變量__forwarding
可以實現無論__block
變量配置在棧上還是堆上時都能夠正確地訪問__block
變量。
Block 循環(huán)引用
如果在 Block 中使用附有 __strong 修飾符的對象团赏,那么當 Block 從棧復制到堆時箕般,該對象為 Block 所持有,容易引起循環(huán)引用舔清。如下代碼
typedef void (^blk_t)(void);
@interface MyObject : NSObject {
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@", obj_);};
return self;
}
@end
編譯器會產生警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle丝里。但很多時候編譯器無法判斷是否產生循環(huán)引用可柿,我們在使用 block 的時候要特別小心,避免循環(huán)引用可以使用 __weak 修飾符丙者。
id __weak obj = obj_;
blk_ = ^{NSLog(@"obj_ = %@", obj);};