Block 是一種帶有自動變量值的匿名函數。git
Block 在 iOS 平常開發中會頻繁使用到,使用起來也十分方便,而它的實現原理和機制不少小夥伴卻一無所知。 Block 是一種帶有自動變量值的匿名函數,它可以自動捕獲函數內使用到的參數,本文將從細節分析 Block 的實現原理。github
在探尋 Block 實現原理中,命令行工具Clang
是很是實用的,它能夠將其轉換成 C++ 源碼,方便咱們瞭解其中的實現原理。編程
clang -rewrite-objc main.m
複製代碼
咱們能夠利用上面的命令,嘗試將下面這段代碼轉換成 C++ 源碼,進而分析 Block 的具體實現:數組
int main(int argc, const char * argv[]) {
@autoreleasepool {
int tempVar = 1;
void (^blk)(void) = ^() {
printf("Block var:%d\n", tempVar);
};
blk();
}
return 0;
}
複製代碼
轉換並剔除多餘代碼後以下:安全
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tempVar;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int tempVar = __cself->tempVar; // bound by copy
printf("Block var:%d\n", tempVar);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int tempVar = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
複製代碼
轉換後獲得了一大串代碼,接下來咱們一一分析這段代碼的實際意義。bash
第一部分是__block_impl
,它是Block
實現的最底層的結構體:框架
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
第二部分__main_block_desc_0
是一個管理Block
內存佔用大小的結構體:函數
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
複製代碼
第三部分爲Block
實現結構體:工具
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tempVar;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
該結構體的命名邏輯爲__函數名_block_impl_函數內順序
,接下來查看結構體成員:ui
__block_impl
類型結構體,參考第一部分。__main_block_impl_0
結構體實例大小。__main_block_impl_0
結構體的構造函數。第四部分爲Block
的函數指針指向的函數__main_block_func_0
:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int tempVar = __cself->tempVar; // bound by copy
printf("Block var:%d\n", tempVar);
}
複製代碼
__main_block_impl_0
結構體中將捕獲的自動變量值做爲成員變量,調用時先獲取結構體成員變量的值,而後複製使用。
第五部分爲main
函數轉換後源碼:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int tempVar = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
複製代碼
除了一個局部變量tempVar
以外,另外 2 行代碼分別是Block
的的初始化部分和調用部分。去除部分類型強轉代碼後以下:
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
(blk->FuncPtr)(blk);
複製代碼
第一行代碼是將前面聲明的__main_block_func_0
和&__main_block_desc_0_DATA
傳入構造函數,獲得blk
結構體實例。
第二行代碼則是調用blk
的函數指針。
在 Block 中使用外部的局部變量時,會自動捕獲該變量而且成爲 Block 結構體的成員變量,以便在 Block 內部訪問該變量。除此以外,有其餘幾種方式能夠訪問外部變量,下面是變量類型和對應的做用域:
經過將下面的代碼轉換至 C++ 代碼,分析 Block 中各類類型變量的訪問方式:
static char globalVar[] = {"globalVar"};
static char globalStaticVar[] = {"globalStaticVar"};
void catchVar() {
int var1 = 1;
int var2 = 2;
static char staticVar[] = {"staticVar"};
void (^blk)(void) = ^{
printf("%d\n", var1);
printf("%s\n", staticVar);
printf("%s\n", globalVar);
printf("%s\n", globalStaticVar);
};
blk();
}
複製代碼
上面的代碼分別使用了局部變量、靜態變量、全局變量和靜態全局變量,其轉換後的代碼:
struct __catchVar_block_impl_0 {
struct __block_impl impl;
struct __catchVar_block_desc_0* Desc;
int var1; // 局部變量
char (*staticVar)[10]; // 靜態變量
__catchVar_block_impl_0(void *fp, struct __catchVar_block_desc_0 *desc, int _var1, char (*_staticVar)[10], int flags=0) : var1(_var1), staticVar(_staticVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchVar_block_func_0(struct __catchVar_block_impl_0 *__cself) {
int var1 = __cself->var1; // bound by copy
char (*staticVar)[10] = __cself->staticVar; // bound by copy
printf("%d\n", var1);
printf("%s\n", (*staticVar));
printf("%s\n", globalVar);
printf("%s\n", globalStaticVar);
}
static struct __catchVar_block_desc_0 {
size_t reserved;
size_t Block_size;
} __catchVar_block_desc_0_DATA = { 0, sizeof(struct __catchVar_block_impl_0)};
void catchVar() {
int var1 = 1;
int var2 = 2;
static char staticVar[] = {"staticVar"};
void (*blk)(void) = ((void (*)())&__catchVar_block_impl_0((void *)__catchVar_block_func_0, &__catchVar_block_desc_0_DATA, var1, &staticVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
經過__catchVar_block_func_0
函數和 Block 構造函數能夠知道各類變量的訪問方式:
下面的代碼中,Block 內使用了外部的一個對象,這種狀況下 Block 內部是如何捕獲該對象的呢?
void catchObject() {
id obj = [NSObject new];
void (^blk)(void) = ^{
printf("%d\n", [obj hash]);
};
blk();
}
複製代碼
咱們將上面的代碼轉換成 C++ 代碼後分析其中實現原理:
struct __catchObject_block_impl_0 {
struct __block_impl impl;
struct __catchObject_block_desc_0* Desc;
__strong id obj;
__catchObject_block_impl_0(void *fp, struct __catchObject_block_desc_0 *desc, __strong id _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchObject_block_func_0(struct __catchObject_block_impl_0 *__cself) {
__strong id obj = __cself->obj; // bound by copy
printf("%d\n", ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("hash")));
}
static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __catchObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchObject_block_impl_0*, struct __catchObject_block_impl_0*);
void (*dispose)(struct __catchObject_block_impl_0*);
} __catchObject_block_desc_0_DATA = { 0, sizeof(struct __catchObject_block_impl_0), __catchObject_block_copy_0, __catchObject_block_dispose_0};
void catchObject() {
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
首先來看catchObject()
函數,在構建 blk 時,傳入了對象obj
和十進制標誌位570425344
。
void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
複製代碼
Block 結構體中成員變量obj
爲__strong
修飾符,傳入的對象obj
直接賦值給成員變量,說明是直接使用原對象而且使引用計數 +1 。
其次是源代碼中新增了兩個方法,分別是__catchObject_block_copy_0
和__catchObject_block_dispose_0
,而這兩個方法又分別調用了_Block_object_assign
和_Block_object_dispose
方法,這兩個方法是用來管理 Block 中變量存儲的,後面會進行分析。
static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼
將一個 Block 做爲另一個 Block 內的參數來使用,接下來分析這種狀況下 Block 的實現。
void catchBlock() {
void (^block)(void) = ^{};
void (^blk)(void) = ^{
block;
};
blk();
}
複製代碼
轉換後代碼以下:
struct __catchBlock_block_impl_0 {
struct __block_impl impl;
struct __catchBlock_block_desc_0* Desc;
__catchBlock_block_impl_0(void *fp, struct __catchBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlock_block_func_0(struct __catchBlock_block_impl_0 *__cself) {
}
static struct __catchBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __catchBlock_block_desc_0_DATA = { 0, sizeof(struct __catchBlock_block_impl_0)};
struct __catchBlock_block_impl_1 {
struct __block_impl impl;
struct __catchBlock_block_desc_1* Desc;
struct __block_impl *block;
__catchBlock_block_impl_1(void *fp, struct __catchBlock_block_desc_1 *desc, void *_block, int flags=0) : block((struct __block_impl *)_block) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlock_block_func_1(struct __catchBlock_block_impl_1 *__cself) {
void (*block)() = (void (*)())__cself->block; // bound by copy
block;
}
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}
static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}
static struct __catchBlock_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlock_block_impl_1*, struct __catchBlock_block_impl_1*);
void (*dispose)(struct __catchBlock_block_impl_1*);
} __catchBlock_block_desc_1_DATA = { 0, sizeof(struct __catchBlock_block_impl_1), __catchBlock_block_copy_1, __catchBlock_block_dispose_1};
void catchBlock() {
void (*block)(void) = ((void (*)())&__catchBlock_block_impl_0((void *)__catchBlock_block_func_0, &__catchBlock_block_desc_0_DATA));
void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
一樣的先看catchBlock()
函數,其中 Block 構造函數中傳入了block
和標誌位570425344
,賦值給在__catchBlock_block_impl_1
結構體中的一個__block_impl
類型的結構體成員變量block
。
void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
複製代碼
在這段代碼中一樣有__catchObject_block_copy_0
和__catchObject_block_dispose_0
兩個方法,不一樣的是調用_Block_object_assign
和_Block_object_dispose
方法時最後一個入參爲7 /*BLOCK_FIELD_IS_BLOCK*/
,以前的捕獲對象時傳入的參數是3 /*BLOCK_FIELD_IS_OBJECT*/
。
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {
_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {
_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
複製代碼
Block 將外部的變量捕獲後,能夠在內部訪問外部的變量,可是還不能修改外部變量的值(靜態變量、全局變量和靜態全局變量能夠直接修改)。這個時候須要使用 __block 修飾符,使得在 Block 內部也能夠修改 __block 修飾符修飾的變量。
下面經過轉換源碼來分析實現原理:
void catchBlockVar() {
__block int blockVar = 1;
void (^blk)(void) = ^{
blockVar = 2;
printf("%d\n", blockVar);
};
blk();
}
複製代碼
轉換後:
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
int blockVar;
};
struct __catchBlockVar_block_impl_0 {
struct __block_impl impl;
struct __catchBlockVar_block_desc_0* Desc;
__Block_byref_blockVar_0 *blockVar; // by ref
__catchBlockVar_block_impl_0(void *fp, struct __catchBlockVar_block_desc_0 *desc, __Block_byref_blockVar_0 *_blockVar, int flags=0) : blockVar(_blockVar->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
__Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
(blockVar->__forwarding->blockVar) = 2;
printf("%d\n", (blockVar->__forwarding->blockVar));
}
static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __catchBlockVar_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlockVar_block_impl_0*, struct __catchBlockVar_block_impl_0*);
void (*dispose)(struct __catchBlockVar_block_impl_0*);
} __catchBlockVar_block_desc_0_DATA = { 0, sizeof(struct __catchBlockVar_block_impl_0), __catchBlockVar_block_copy_0, __catchBlockVar_block_dispose_0};
void catchBlockVar() {
__attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};
void (*blk)(void) = ((void (*)())&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
和其餘類型 Block 最大的不一樣就是多了一個結構體__Block_byref_blockVar_0
,通過__block
修飾符修飾的變量都會自動生成一個這樣的結構體。
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
int blockVar;
};
複製代碼
在catchBlockVar
方法轉換後,以前的int
類型變量blockVar
變成__Block_byref_blockVar_0
類型結構體,而後將此結構體地址傳入 Block 的構造函數中,所以 Block 自動生成的成員變量也爲__Block_byref_blockVar_0
類型。
void catchBlockVar() {
__Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};
void (*blk)(void) = (&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
在被 Block 被調用時,經過下面的代碼能夠發現,訪問blockVar
並不直接訪問,而是經過其__forwarding
來訪問其原變量的值。這樣能夠在它被複制到堆區時,訪問堆區中的結構體。爲何要優先訪問堆區的結構體?棧區的對象在超出其做用域後會被釋放,若是但願在做用域外使用就須要複製到堆區中。
static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
__Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
(blockVar->__forwarding->blockVar) = 2;
printf("%d\n", (blockVar->__forwarding->blockVar));
}
複製代碼
在這段代碼中的__catchObject_block_copy_0
和__catchObject_block_dispose_0
兩個方法中傳入的參數是8 /*BLOCK_FIELD_IS_BYREF*/
。
static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {
_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {
_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼
在 Block 使用__block
修飾的變量和__block
修飾的對象,其中內部實現是有一些細微區別的,經過下面的代碼來進行分析。
void catchBlockObject() {
__block NSObject *obj = [[NSObject alloc] init];
blk_t block = ^ {
obj;
};
}
複製代碼
轉換後:
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __catchBlockObject_block_impl_0 {
struct __block_impl impl;
struct __catchBlockObject_block_desc_0* Desc;
__Block_byref_obj_1 *obj; // by ref
__catchBlockObject_block_impl_0(void *fp, struct __catchBlockObject_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlockObject_block_func_0(struct __catchBlockObject_block_impl_0 *__cself) {
__Block_byref_obj_1 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj);
}
static void __catchBlockObject_block_copy_0(struct __catchBlockObject_block_impl_0*dst, struct __catchBlockObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __catchBlockObject_block_dispose_0(struct __catchBlockObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __catchBlockObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlockObject_block_impl_0*, struct __catchBlockObject_block_impl_0*);
void (*dispose)(struct __catchBlockObject_block_impl_0*);
} __catchBlockObject_block_desc_0_DATA = { 0, sizeof(struct __catchBlockObject_block_impl_0), __catchBlockObject_block_copy_0, __catchBlockObject_block_dispose_0};
void catchBlockObject() {
__attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
blk_t block = ((void (*)())&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製代碼
在聲明的結構體__Block_byref_obj_1
中,和以前不同的是多了__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
兩個管理內存的方法。
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
複製代碼
再看到catchBlockObject()
函數中,被__block
修飾符修飾的obj
對象轉換成__Block_byref_obj_1
類型結構體。其中copy
和dispose
兩個方法傳入的__Block_byref_id_object_copy_131
、__Block_byref_id_object_dispose_131
兩個靜態方法。
void catchBlockObject() {
__Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
blk_t block = (&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製代碼
靜態方法以下,在最後一個參數傳入的是131
,其實就是3 + 128
。
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製代碼
經過下面這個枚舉能夠明白,爲何捕獲不一樣類型的變量,須要不一樣的入參。根據入參不一樣,對捕獲的變量複製和釋放的操做都是不一樣的。131
則表示BLOCK_FIELD_IS_BYREF
|BLOCK_BYREF_CALLER
。
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
複製代碼
Block 自己的 Copy / Dispose 方法入參仍是8 /*BLOCK_FIELD_IS_BYREF*/
。
Block 的存儲域分爲 3 種,分別爲_NSConcreteStackBlock
、_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
。
正常狀況下,定義在類內部的 Block 在捕獲了自動變量的狀況下都是在棧區,能夠經過下面的代碼打印出其類型。可是在實際使用中都會定義後都會賦值給一個變量,這會致使實際使用用這個 Block 的時候已經變成_NSConcreteMallocBlock
類型。
// block 使用了捕獲的變量 tempVar
int main(int argc, const char * argv[]) {
@autoreleasepool {
int tempVar = 1;
NSLog(@"Stack Block:%@\n", ^() {
printf("Stack Block! %d\n", tempVar);
});
}
return 0;
}
// printf:Stack Block:<__NSStackBlock__: 0x7ffeefbff4a0>
複製代碼
在定義全局變量的區域定義的 Block 類型爲_NSConcreteGlobalBlock
,另外還有一種狀況就是定義在類內部的 Block 在沒有捕獲任何自動變量時,也是_NSConcreteGlobalBlock
類型。
// block 內未使用外部變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Global Block:%@\n", ^() {
printf("Global Block!\n");
});
}
return 0;
}
// printf:Global Block:<__NSGlobalBlock__: 0x1000021c8>
複製代碼
由於 Block 捕獲的 __block 變量存在棧區時,超出其做用域後則被釋放。爲了不這種狀況,Block 實現機制將 Block 從棧區複製到堆區,這樣即便超出其做用域,堆區的 Block 和 __block 變量依然存在。
在未被複制時,__block 變量__forwarding
指向自身,被複制後指向堆區中的 __block 變量,這種機制使其不管是在堆區仍是棧區均可以正確訪問。
下面這些場景下編譯器會自動處理將 Block 拷貝到堆上:
如下場景須要手動拷貝至堆上
當 Block 從棧區被複制到堆區時,對應的__block
修飾符修飾的變量也相應地被複制到堆區。
在前面的內容咱們分析到__block
修飾的變量會轉換成一個結構體,結構體中含有成員變量__forwarding
,複製到堆區後能夠在 Block 變量超出其做用域使用,這個時候棧區結構體成員變量__forwarding
指向堆區的結構體(在未被複制時指向自身結構體)。
當多個 Block 使用同一個 __block 變量時,複製已經在堆上的 __block 變量引用計數會增長,當釋放時也是將減引用計數減至 0 後才廢棄該 __block 變量。
在第二章節中介紹了捕獲不一樣類型的變量時,Block 調用_Block_object_assign
函數的入參flags
都不同,下面來看看具體實現。
直接看實現源碼:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
複製代碼
由上面代碼可知_Block_object_assign
方法根據入參flags
作了不一樣的處理,下面一一分析不一樣入參的處理方式:
// 默認_Block_retain_object 被賦值爲 _Block_retain_object_default 即什麼都不作
_Block_retain_object(object);
// 指針指向原對象內存地址。
*dest = object;
複製代碼
_Block_retain_object
方法在 _Block_use_RR2
被執行時纔有實際意義。
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
複製代碼
分析_Block_copy
的實現:
//複製或碰撞引用計數。若是確實要複製,請調用複製助手(若是存在)。
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
// 已複製 則增長引用計數
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 若是是全局 Block 則直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 進行復制
else {
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
result->isa = _NSConcreteMallocBlock;
return result;
}
}
複製代碼
分析_Block_byref_copy
的實現:
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 複製 Block_byref
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// 複製 Block_byref2,含有 copy / dispose 方法的變量須要執行這部分代碼
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
// 複製 Block_byref3
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// 已經複製到堆上的 引用計數 +1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
複製代碼
在其餘狀況下都是直接指針指向原對象地址:
*dest = object;
複製代碼
下面是 Block 捕獲變量的釋放邏輯:
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
複製代碼
分析_Block_byref_release
的實現:
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) byref = byref->forwarding; // 判斷是否被複制到堆上 if (byref->flags & BLOCK_BYREF_NEEDS_FREE) { int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; os_assert(refcount); // 引用計數 -1 後判斷是否要進行釋放操做 if (latching_decr_int_should_deallocate(&byref->flags)) { // 判斷這個變量是否有 copy / dispose 方法 if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1); (*byref2->byref_destroy)(byref); } free(byref); } } } 複製代碼
須要根據 _block 變量成員變量和標誌位來判斷釋放步驟。
分析_Block_release
的實現:
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
// 全局 Block 和 棧上的 Block 直接返回
// Block 引用計數 -1 後判斷是否須要進行釋放
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
複製代碼
_Block_release_object(object);
複製代碼
_Block_release_object
方法在 ARC 環境下無操做,和_Block_retain_object
同樣。
一個 Block 做爲類的成員變量的同時,Block 內部還使用了類實例的狀況下會引起循環引用。在這種狀況下,類實例持有成員變量 block ,block 持有成員變量 __block 變量,__block 變量結構體持有類實例,造成一個三角循環引用關係。
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
}
複製代碼
解決循環引用的方法有 2 種,一種是使用 __weak 修飾符,這種方法打破了__block 變量結構體持有類實例的關係,從而避免循環引用。
__weak id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
}
複製代碼
還有一種狀況是使用 __block 修飾符,而後 blk 調用函數最後一行將tmp
手動置空,這種方法雖然也能夠避免循環引用,可是一旦 blk 沒有被調用的話,一樣會形成循環引用。因此仍是使用 __weak 修飾符的方式更爲安全。
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
複製代碼
整片文章寫下來,最重要的幾個概念: