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 部分构成:
1、isa 指针,所有对象都有该指针,用于实现对象相关的功能。
2、flags,用于按 bit 位表示一些 block 的附加信息。
3、reserved,保留变量。
4、invoke,函数指针,指向具体的 block 实现的函数调用地址。
5、descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
6、variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
block的声明与定义如下图:
首先我们来看四个函数
void test1() {
int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 10
}
void test2() {
__block int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 20
}
void test3() {
static int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 20
}
int a = 10;
void test4() {
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block();//20
}
使用clang将test1函数编译成c++代码,在命令行中输入clang -rewrite-objc main.c即可在目录中看到 clang 输出了一个名为 main.cpp 的文件。该文件就是 block在c++语言实现。
注:如若碰到’UIKit/UIKit.h’ file not found之类的错误,请参考Objective-C编译成C++代码报错
#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __test1_block_impl_0 {
struct __block_impl impl;
struct __test1_block_desc_0* Desc;
int a;
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test1_block_func_0(struct __test1_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yt_3w8z3x6s6_53pnlcw6jk8vcr0000gn_T_main_f8cd6c_mi_0, a);
}
static struct __test1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test1_block_desc_0_DATA = { 0, sizeof(struct __test1_block_impl_0)};
void test1()
{
int a = 10;
void (*block)() = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
int main(int argc, char * argv[]) {
test1();
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
可以看到a是作为值传递进去的。 接下来我们打印这3处a的值,结果如下:
2017-08-09 18:51:24.427 TestBounds[21998:295458] block定义前:0x7fff5278c57c
2017-08-09 18:51:24.481 TestBounds[21998:295458] block定义后:0x7fff5278c57c
2017-08-09 18:51:24.481 TestBounds[21998:295458] block内部:0x60000004fc80
a在block中和block外部地址是不一样的,外面的地址是在栈中,而在ARC下block访问了外部变量会拷贝一份到堆上,此时的block并不会对变量a造成影响。
再来看看test2函数编译成c++代码。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __test2_block_impl_0 {
struct __block_impl impl;
struct __test2_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__test2_block_impl_0(void *fp, struct __test2_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 __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yt_3w8z3x6s6_53pnlcw6jk8vcr0000gn_T_main_e4454e_mi_0, (a->__forwarding->a));
}
static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __test2_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
void (*dispose)(struct __test2_block_impl_0*);
} __test2_block_desc_0_DATA = { 0, sizeof(struct __test2_block_impl_0), __test2_block_copy_0, __test2_block_dispose_0};
void test2() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*block)() = ((void (*)())&__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
int main(int argc, char * argv[]) {
test2();
}
可以看到a是作为引用传入函数中去,这是因为使用了__block关键字造成的效果。我们再来打印a的地址。
2017-08-09 19:04:30.735 TestBounds[22354:303315] block定义前:0x7fff5b540578
2017-08-09 19:04:30.863 TestBounds[22354:303315] block定义后:0x60800003fe58
2017-08-09 19:04:30.863 TestBounds[22354:303315] block内部:0x60800003fe58
可以看到使用__block定义的变量在block中拷贝到堆上去后对后来的a的地址也产生了变化,a在定义前是栈区,但只要进入了block区域,就变成了堆区。 总结如下:
-
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的
-
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的
利用上述方法也可以解释test3和test4输出结果原因。
Block的内存管理
在 Objective-C 语言中,一共有 3 种类型的 block:
1、_NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
2、_NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
3、_NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
而在MRC和ARC中有些区别 无论当前环境是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
MRC情况下:
-
block如果如果访问外部变量,block在栈里
-
不能对block使用retain,否则不能保存在堆里
-
只有使用copy才能放在堆里
ARC情况下:
-
block如果访问外部变量,block在堆里
-
block可以使用copy和strong,并且block是一个对象
Block的循环引用
如果要在block中直接使用外部强指针会发生循环引用,使用以下代码在block外部实现可解决
__weak typeof(self) weakSelf = self;
如果在block内部使用延时操作还使用弱指针的话会取不到弱指针,此时需要在block内部再将弱指针强引用一下
__strong typeof(self) strongSelf = weakSelf;
本文参考: