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实现原理:https://www.jianshu.com/p/ee9756f3d5f6
谈Objective-C block的实现:https://blog.devtang.com/2013/07/28/a-look-inside-blocks/