block

基本认识

block本质上是一个封装了函数调用以及函数调用环境的OC对象,内部也包含一个isa指针。

block的底层实现

简单的block底层实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
};

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //函数指针
};

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)};

变量捕获-基础数据类型

auto变量

定义如下一个捕获auto类型变量(离开作用域自动销毁)的block:

1
2
3
4
int age = 10;
void (^block)(void) = ^(){
NSLog(@"%d",age);
};

将其转换为c++代码:

1
2
int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

block底层结构为:

1
2
3
4
5
6
7
8
9
10
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 _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

可见,block在捕获auto变量的方式为值传递。

static变量

定义如下一个捕获static类型变量的block:

1
2
3
4
static int age = 10;
void (^block)(void) = ^(){
NSLog(@"%d",age);
};

将其转换为c++代码:

1
2
static int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));

block底层结构为:

1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

可见,block在捕获static变量的方式为指针传递。

全局变量

定义如下一个捕获全局变量的block:

1
2
3
4
//age此时被定义为全局变量
void (^block)(void) = ^(){
NSLog(@"%d",age);
};

将其转换为c++代码:

1
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

block底层结构为:

1
2
3
4
5
6
7
8
9
10
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;
}
};

可见,全局变量可以直接使用,不会被block捕获。

总结

变量捕获-对象类型

当block内部访问了对象类型的auto变量时:

  • 如果block是在栈上,将不会对auto变量产生强引用
  • 如果block被拷贝到堆上:

    • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果block从堆上移除:

    • 会调用block内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放引用的auto变量(release)

此时block的底层相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

block的类型

blcok有三种类型,都继承自NSBlock。可以通过class方法或者isa指针查看其具体类型。

  • __NSGlobalBlock__ (_NSConcreteGlobalBlock) :没有访问auto变量
  • __NSStackBlock__ (_NSConcreteStackBlock : 访问了auto变量
  • __NSMallocBlock__ (_NSConcreteMallocBlock): __NSStackBlock\调用了copy方法

下面代码的输出结果为:__NSGlobalBlock__

1
2
3
4
void (^block)(void) = ^(){
NSLog(@"Hello World");
};
NSLog(@"%@",[block class]);

下面代码ARC环境下的输出结果为:__NSMallocBlock__,MRC的输出结果为:__NSStackBlock__

1
2
3
4
5
int age = 10;
void (^block)(void) = ^(){
NSLog(@"%d",age);
};
NSLog(@"%@",[block class]);

下面代码的输出结果为:__NSStackBlock__

1
2
3
4
int age = 10;
NSLog(@"%@",[^{
NSLog(@"%d",age);
} class]);

三种类型block的内存存储以及每一种类型的block调用copy后的结果如下所示:

__block修饰符

底层实现

__block可以用于解决block内部无法修改auto变量值的问题。__block不能修饰全局变量、静态变量(static)。

1
2
3
4
5
6
7
__block int age = 10;
__block NSObject *obj = [[NSObject alloc] init];

void (^block)(void) = ^{
obj = nil;
age = 20;
};

上面的代码会被编译为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_1 *obj; // by ref
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};

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;
};

//实际执行的block代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_obj_1 *obj = __cself->obj; // bound by ref
__Block_byref_age_0 *age = __cself->age; // bound by ref

(obj->__forwarding->obj) = __null;
(age->__forwarding->age) = 20;
}

可见,__block修饰的变量会被包装成一个对象。

__block变量的内存管理

当block在栈上时,并不会对__block变量产生强引用。

当block被copy到堆时:

  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会对__block变量形成强引用(retain)[仅ARC, MRC不会retain]

当block从堆中移除时:

  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)

__forwarding指针

为什么会指向自己呢,原因是为了确保当栈中的Block复制到堆中的时候,在栈中仍然能正确访问堆中的变量。

循环引用

循环引用产生的原因:

解决循环引用:

  • ARC: __weak、__unsafe_unretained
  • MRC: __block