Block

Block

Block本质是一个NSBlock类型的OC对象,内部有isa指针

Block封装了函数调用以及函数调用环境

数据结构

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
41
42
// eg:
// int someVar = 20
// void (^block)(void) = ^{
// NSLog("var is %d", age);
//}
// block();

struct __mainblock_impl_0 {-
struct __blockimpl impl; // 见下方__block_impl
struct __main_block_desc_0* Desc; // 见下方__main_block_desc_0
int someVar; // 捕获的变量

// 构造函数,返回结构体对象
__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; // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // 在这里计算了Block的大小

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 这里是block内部的代码
}


// 执行block的代码 (已经经过简化)
block->FuncPtr(block);

变量的捕获(capture)

  • 局部变量
    • auto (捕获, 值传递)
    • static (捕获, 指针传递)
  • 全局变量 (不捕获, 直接访问)

Block的类型

Block有3种类型,可以通过class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • _NSGlobalBlock_(_NSConcreteGlobalBlock) -> 数据区(.data)
    • 没有访问auto变量
  • _NSStackBlock_(_NSConcreteStackBlock) -> 堆
    • 访问了auto变量
  • _NSConcreteStackBlock_(_NSConcreteMallocBlock) -> 栈
    • __NSStackBlock__调用了copy

Block的copy

ARC环境下,编译器会自动将栈上的block复制到堆上,比如如下情况

  • block作为函数返回值时
  • 将block赋值给__strong指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD的方法参数

对象类型的auto变量

当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修饰符

__block可以用于解决block内部无法修改auto变量值的问题

__block不能修饰静态变量(static)、全局变量

编辑器会将__block变量包装成一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__block int age = 10;
^{
NSlog(@"%d", age);
}();

// block对象编译成的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // int age 包装的对象
};

// 生成的age对象对应的结构体
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // 指向自己,用于指向堆上的对象copy
int __flags;
int __size;
int age;
};