1.Block 的定義
In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.
意思就是,在編程語言中,閉包是一個引用外部變量(有時候也稱作自由變量)的,函數(或指向函數的指針)。
Objective-C 中的 block 其實就是對于閉包的實現。
2.Block 的結構
block 的數據結構定義如下:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
通過該數據結構,我們可以知道,一個 block 實例實際上由 6 部分構成:
- isa 指針,所有對象都有該指針,用于實現對象相關的功能。
- flags,用于按 bit 位表示一些 block 的附加信息, block copy 的實現代碼可以看到對該變量的使用。
- reserved,保留變量。
- invoke,函數指針,指向具體的 block 實現的函數調用地址。
- descriptor, 表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。
- variables,capture 過來的變量,block 能夠訪問它外部的局部變量,就是因為將這些變量(或變量的地址)復制到了結構體中。
Objective-C中,有3種 block 類型:
- _NSConcreteGlobalBlock 全局的靜態 block。
- _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。
- _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。
3 種 block 示例如下:
typedef void (^Block)(id info);
static NSString * const kTestDemo = @"TestDemo";
- (void)blockTest1 {
int i = 0;
Block block1 = ^(id info) {
NSLog(@"Block1 %d", i);
};
NSLog(@"%@", ^{NSLog(@"Hello, world!");}); //__NSGlobalBlock__
NSLog(@"%@", ^{NSLog(@"%@.Hello, world!", kTestDemo);}); //__NSGlobalBlock__
NSLog(@"%@", ^{NSLog(@"%d.Hello, world!", i);}); //__NSStackBlock__
NSLog(@"%@", [^{NSLog(@"%d.Hello, world!", i);} copy]); //__NSMallocBlock__
NSLog(@"%@", block1); //__NSMallocBlock__
}
然后,我們先來研究下 NSGlobalBlock 的底層實現,先新建一個名為 block.c 的源文件,然后用
clang -rewrite-objc block.c
將其轉化為 C++ 實現,生成 block.cpp 文件。
block.c 源代碼如下:
#include <stdio.h>
int main()
{
^{ printf("Hello, World!\n"); } ();
return 0;
}
block.cpp 文件中的關鍵代碼如下:
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;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello, World!\n");
}
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()
{
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
return 0;
}
從中我們可以看出,__main_block_impl_0 就是 block 的實現,從 block 的結構體我們可以看出:
- 它主要由一個 impl 、 一個 descriptor 和一個構造函數 __main_block_impl_0 組成。
- 該結構體的前兩個參數也都是結構體,第二個參數Desc中,是關于結構體的大小、版本升級所需保留參數,暫不做過多解析。
- 第一個結構體參數的結構體中,
- 看到了 isa 指針,說明 block 也是 Objective-C 中的對象,isa 指向對象的一個 8 字節;
- Flags 和 Reserved 是某些標記字段,暫不過多解釋;
- FuncPtr 函數指針,其實就是block所需執行的代碼段,存放的是地址
- 結構體的構造函數中,
- 傳入的第一個參數,就是函數指針,impl.FuncPtr = fp;
- block 的 isa 指向 _NSConcreteStackBlock 指針,也就是說,當一個 block 被聲明的時候,它都是一個 _NSConcreteStackBlock 類的對象;
- 可以看出,構造函數是對block的初始化
- __main_block_impl_0 函數中其實存儲著我們block中寫下的代碼。
block 的底層數據結構可以用下面一張圖來展示:
我們再來看 main() 函數實現,把其中的類型都去掉,如下
/* 調用結構體函數初始化
__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
由此,可以看出,__main_block_func_0 函數的地址和 __main_block_desc_0_DATA (也就是 { 0, sizeof(struct __main_block_impl_0) } )的地址,傳入到了 __main_block_impl_0 的構造函數里,被保存到block的結構體中。
3.Block 的變量獲取
1) 獲取局部變量的 block 結構體
首先,用 clang 命令將 Objective-C 代碼轉換為 C++ 實現,其中 Objective-C 源碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 0;
static int b = 1;
void (^Block)(void) = ^{
NSLog(@"a = %d, b = %d", a, b);
};
a = 2;
b = 3;
Block();
}
return 0;
}
//打印結果為:a = 0; b = 3;
轉換的 C++ 實現如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; //值
int *b; //指針
//1.
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//2.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_e0fd9d_mi_0, a, (*b));
}
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 a = 0;
static int b = 1;
//3.
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
a = 2;
b = 3;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
從上面可以看出,__main_block_impl_0 結構體中多了兩個變量,用來保存block獲取的外部的變量,其中 a 是 int 類型,b 是 int * 類型,也就是 a 保存的是變量的值,b 保存的是變量的指針;從 3. 處代碼就可以看出 b 靜態局部變量傳入的是變量的地址。
2)獲取全局變量的 block 結構體
同樣,用 clang 命令將 Objective-C 代碼轉換為 C++ 實現,其中 Objective-C 源碼如下:
int a = 0;
static int b = 1;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^Block)(void) = ^{
NSLog(@"a = %d, b = %d", a, b);
};
a = 2;
b = 3;
Block();
}
return 0;
}
//打印結果為:a = 2; b = 3;
轉換的 C++ 實現如下:
int a = 0;
static int b = 1;
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_eaada3_mi_0, a, b);
}
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;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
a = 2;
b = 3;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
由上可以看出,block 根本就沒有 capture 全局變量保存到自己的結構體中,而是直接調用并賦值,這是因為局部變量因為跨函數訪問所以需要捕獲,全局變量在哪里都可以訪問,所以不用捕獲。
總結:
變量類型 捕獲到 block 內部 訪問方式 局部變量 ? 值傳遞 靜態局部變量 ? 指針傳遞 全局變量 ? 直接訪問
3)block 是怎么獲取捕獲的 self 的
以下代碼中 block 是否會捕獲變量呢?
#import "Person.h"
@interface Person ()
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
- (void)test1 {
void(^block)(void) = ^{
NSLog(@"實例方法:%@",self);
};
block();
}
+ (void)test2 {
NSLog(@"類方法.");
}
@end
同樣,轉換為 C++ 代碼查看其內部結構
static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) {
if (self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) {
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
}
return self;
}
struct __Person__test1_block_impl_0 {
struct __block_impl impl;
struct __Person__test1_block_desc_0* Desc;
Person *self; //捕獲到self指針
__Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy 取出block中 self 的值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_0,self); //使用上面取到的 self 的值
}
static void __Person__test1_block_copy_0(struct __Person__test1_block_impl_0*dst, struct __Person__test1_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test1_block_dispose_0(struct __Person__test1_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Person__test1_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test1_block_impl_0*, struct __Person__test1_block_impl_0*);
void (*dispose)(struct __Person__test1_block_impl_0*);
} __Person__test1_block_desc_0_DATA = { 0, sizeof(struct __Person__test1_block_impl_0), __Person__test1_block_copy_0, __Person__test1_block_dispose_0};
static void _I_Person_test1(Person * self, SEL _cmd) { //test1實例方法默認傳入兩個參數,一個是 self ,一個是 _cmd (函數具體實現的函數指針)
void(*block)(void) = ((void (*)())&__Person__test1_block_impl_0((void *)__Person__test1_block_func_0, &__Person__test1_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
static void _C_Person_test2(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_1);
}
從上面可以看出,不論對象方法還是類方法都會默認將 self 作為參數傳遞給方法內部,既然是作為參數傳入,那么 self 肯定是局部變量。就如上面得到的結論局部變量肯定會被 block 捕獲。
那么,接下來我們來看一下如果在 block 中使用成員變量或者調用實例的屬性會有什么不同的結果。
- (void)test1 {
void(^block)(void) = ^{
NSLog(@"%@",self.name);
NSLog(@"%@",self->_name);
};
block();
}
struct __Person__test1_block_impl_0 {
struct __block_impl impl;
struct __Person__test1_block_desc_0* Desc;
Person *self;//這里同樣只是捕獲了self
__Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));//屬性這里是調用 objc_msgSend 發送消息
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_1,(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)));//成員變量這里是直接訪問內存
}
3)block 的類型
從上面的 C++ 實例代碼中發現,block 構造函數中的 isa 都是指向
_NSConcreteStackBlock 類對象地址,而在 Objective-C 中打印,能得出三種類型的 block ,這是為什么呢?
首先,我們先來看一下 block 的類型打印
int a = 0;
static int b = 1;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^Block)(void) = ^{
NSLog(@"a = %d, b = %d", a, b);
};
a = 2;
b = 3;
Block();
NSLog(@"%@", [Block superclass]);
NSLog(@"%@", [[Block superclass] superclass]);
NSLog(@"%@", [[[Block superclass] superclass] superclass]);
NSLog(@"%@", [[[[Block superclass] superclass] superclass] superclass]);
NSLog(@"%@", [[[[Block superclass] superclass] superclass] class]); // NSObject
}
return 0;
}
//打印結果如下:
//__NSGlobalBlock
//NSBlock
//NSObject
//(null)
//NSObject
從上,可以看出 block 本質其實就是 Objective-C 對象。
block 的類型又是如何定義呢?如下圖所示:
block類型 環境 內存區域 NSGlobalBlock 沒有訪問 auto 變量 數據段 NSStackBlock 訪問了auto變量 棧 NSMallocBlock NSStackBLock 調用了copy 堆
經分析可知,ARC 條件下,編譯器會根據情況自動將棧上的 block 進行一次 copy 操作,將 block 復制到堆上,那么什么情況下 ARC 會自動將 block 進行一次 copy 操作呢?
- block 作為函數返回值時;
typedef void (^Block)(void);
Block myblock() {
int a = 10;
// 上文提到過,block中訪問了auto變量,此時block類型應為__NSStackBlock__
Block block = ^{
NSLog(@"---------%d", a);
};
return block;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block = myblock();
block();
// 打印block類型為 __NSMallocBlock__
NSLog(@"%@",[block class]);
}
return 0;
}
- 將 block 賦值給 __strong 指針時;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// block內沒有訪問auto變量
Block block = ^{
NSLog(@"block---------");
};
NSLog(@"%@",[block class]);
int a = 10;
// block內訪問了auto變量,但沒有賦值給__strong指針
NSLog(@"%@",[^{
NSLog(@"block1---------%d", a);
} class]);
// block賦值給__strong指針
Block block2 = ^{
NSLog(@"block2---------%d", a);
};
NSLog(@"%@",[block1 class]);
}
return 0;
}
//打印如下:
// __NSGlobalBlock__
// __NSStackBlock__
// __NSMallocBlock__
- block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數時;
例如:遍歷數組的 block 方法,將 block 作為參數的時候。
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"array = %@, idx = %lu, value = %@", array, (unsigned long)idx, obj);
}];
- block 作為 GCD API 的方法參數時;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self = [[YYFileManager alloc] init];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
那么又回到上面的問題,為什么 C++ 中指向的都是 _NSConcreteStackBlock 呢?大膽的猜測一下,其是不是在 runtime 運行時過程中對 block 類型進行了轉變,最終 block 類型以 runtime 運行時類型,也就是我們打印出的類型為準。
來看一下 block 被 copy 的示例代碼:
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1.
if (!arg) return NULL;
// 2.
aBlock = (struct Block_layout *)arg;
// 3.
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4.
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6.
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7.
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8.
result->isa = _NSConcreteMallocBlock;
// 9.
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
從中可以看出,在第 8 步,目標的 block 類型被修改為 _NSConcreteMallocBlock。(代碼來自這里<html>http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy</html>)
那 block 怎么釋放呢,這里分 6 個步驟:
void _Block_release(void *arg) {
// 1.
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2.
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3.
if (newCount > 0) return;
// 4.
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5.
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6.
else {
printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock);
}
}
4)__block 實現原理
我們都知道,要想在 block 內修改外部基礎變量(float、int、long等),需要在前面加修飾符 __block ,那么它是如何實現修改呢,我們來看源碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 0;
NSLog(@"Block 外:a = %d , &a = %p", a, &a);
NSLog(@"Block 外:a = %d , &a = %p", a, &a);
void (^Block)(void) = ^{
a = 1;
NSLog(@"Block 內:a = %d , &a = %p", a, &a);
};
a = 2;
Block();
}
return 0;
}
//打印結果:
//Block 外:a = 0 , &a = 0x7ffeefbff508
//Block 內:a = 1 , &a = 0x10072c0a8
從打印結果看出,a 的地址變了,而且變化挺大的,從地址和上面分析可以看出,a 變量從棧區被 copy 了一份到堆區,我們再來看看 C++ 源碼:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_bb7d34_mi_0, (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 2;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
1)從第一個結構體 struct __Block_byref_a_0 可以看出,__block 修飾的變量 a 被轉化為了一個結構體,這個結構體有5個成員變量:
- isa 指針,說明它由基礎變量變成了OC對象
- forwarding 指針,這個后面再詳細解釋
- flags,標記用的
- size,該結構體的大小
- a,變量值
2)從構造函數 __main_block_func_0 中 (a->__forwarding->a) = 1 和 mian 函數中 (a->__forwarding->a) = 2 可以看出,棧區的變量 a 的 __forwarding 指針指向堆區的 a 變量的結構體,而堆中 a 變量的結構體的 __forwrding 指針指向自己,如圖:
而在 MRC 中,即便 blcok 捕獲了__block 變量,也不會從棧上 copy 到堆上,需要自己手動處理,那么這時候變量結構體的 __forwarding 指向自己,如圖:
如果,__block 修飾 OC 對象呢,它會是什么樣的呢?源碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block id block_obj = [[NSObject alloc]init];
id obj = [[NSObject alloc]init];
NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
void (^myBlock)(void) = ^{
NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
};
myBlock();
}
return 0;
}
//打印結果:
//block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]
//Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]
編譯后的 C++ 代碼如下:
struct __Block_byref_block_obj_0 {
void *__isa;
__Block_byref_block_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id block_obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id obj;
__Block_byref_block_obj_0 *block_obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
id obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __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"))};
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
首先,OC 中,ARC 默認聲明自帶 __strong 所有權修飾符,所以 main 函數的默認聲明如如下:
__block id __strong block_obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];
綜上,可以看出ARC環境下,block 捕獲外部對象變量,是會 copy 一份的,可以看出他們的地址不同。block 內會保留強引用修飾的對象,如果不做處理,就會產生循環引用。
參考文獻:
clang命令相關:https://cotin.tech/iOS/clang-rewrite-objc/
Block底層原理:https://juejin.im/post/5b0181e15188254270643e88
__block實現原理:http://www.lxweimin.com/p/ee9756f3d5f6
談Objective-C block的實現:https://blog.devtang.com/2013/07/28/a-look-inside-blocks/