发布于 

Block 梳理

本篇是对 Block 的梳理。

Block 的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 局部变量
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

// 属性
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

// 方法参数
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

// 调用方法
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

// typedef
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

日常开发中,创建 block 常会通过 typedef + property 的方式。

Block 的底层

将如下代码通过 clang 的 rewrite 指令生成 c/c++ 描述。

1
2
3
4
5
6
7
8
int main(int argc, const char * argv[]) {
void(^block)() = ^()
{
NSLog(@"hello");
};
block();
return 0;
}

clang rewrite 后:

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
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_3d0d97_mi_0);
}

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

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


int main(int argc, const char * argv[]) {
void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

会看到几个部分:

  1. __main_block_func_0

    • 一个执行函数
  2. __main_block_desc_0

    • 一个 Block 描述结构体
  3. __block_impl

    • 这是 Block 对象
    • isa:ARC 下有三种类型,分别是 _NSConcreteStackBlock_NSConcreteMallocBlock_NSConcreteGlobalBlock
    • Flags:标志位,分别有:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      enum {
      BLOCK_DEALLOCATING = (0x0001), // runtime
      BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
      BLOCK_NEEDS_FREE = (1 << 24), // runtime
      BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
      BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
      BLOCK_IS_GC = (1 << 27), // runtime
      BLOCK_IS_GLOBAL = (1 << 28), // compiler
      BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
      BLOCK_HAS_SIGNATURE = (1 << 30) // compiler
      };
    • Resetved:保留位
    • FuncPtr:block 执行的函数指针地址
  4. __main_block_impl_0

    • 结构体包含了一个__block_impl__main_block_desc_0 结构体, 还有一个初始化方法
    • 赋值相应字段
  5. main 函数

    • void(^block)() 转化为第一段
    • 让 block 这个变量指针指向新生产的 __main_block_impl_0 结构体变量。
    • 调用的构造器传入函数地址(__main_block_func_0)和描述字段(__main_block_desc_0_DATA)。
    • block(); 转化为第二段
    • 大致流程:取出 impl -> 调用实现函数指针(impl.FuncPtr)-> 传入 block(自身)

简而言之,在 main 函数中,先通过 block、func、desc 新建一个 impl,这样就将 block 与 func 进行了绑定。在 block 执行时,执行 block 对应 impl 内的 func 函数。

变量捕获

我们知道是 Block 中基础类型的变量会被拷贝值,指针变量会捕获指针变量并强引用。

先模拟一个 block 捕获外部变量的过程:

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, const char * argv[]) {
int i = 0;
NSMutableArray *n = [NSMutableArray array];
void(^block)() = ^()
{
int a = i;
[n addObject:@"1"];
NSLog(@"hello");
};
block();
return 0;
}

clang resrite 后的代码:

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
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
NSMutableArray *n;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, NSMutableArray *_n, int flags=0) : i(_i), n(_n) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
NSMutableArray *n = __cself->n; // bound by copy

int a = i;
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)n, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_0);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_1);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->n, (void*)src->n, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->n, 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[]) {


int i = 0;
NSMutableArray *n = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i, n, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

看到 __main_block_impl_0 多出了 2 个变量:

1
2
int i;
NSMutableArray *n;

变量名与我们捕获的一致。即当 Block 捕获一外部变量时,在 Block 内部会维护一个该变量对应的类型变量(基础类型为基础类型,对象为对象的指针并强引用)。

__block 的底层

我们知道,在block 中如果想改变一个外部变量的值,需要为该变量声明 __block

__block 演示代码:

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, const char * argv[]) {
__block int i = 0;
__block NSMutableArray *n = [NSMutableArray array];
void(^block)() = ^()
{
i = 1;
n = [NSArray array];
NSLog(@"hello");
};
block();
return 0;
}

clang rewrite 后的代码:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __Block_byref_n_1 {
void *__isa;
__Block_byref_n_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *n;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__Block_byref_n_1 *n; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, __Block_byref_n_1 *_n, int flags=0) : i(_i->__forwarding), n(_n->__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_i_0 *i = __cself->i; // bound by ref
__Block_byref_n_1 *n = __cself->n; // bound by ref

(i->__forwarding->i) = 1;
(n->__forwarding->n) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_ea2303_mi_0);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->n, (void*)src->n, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->n, 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[]) {

__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
__attribute__((__blocks__(byref))) __Block_byref_n_1 n = {(void*)0,(__Block_byref_n_1 *)&n, 33554432, sizeof(__Block_byref_n_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, (__Block_byref_n_1 *)&n, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

可以看到 __main_block_impl_0 中多处了两个结构体对象 __Block_byref_i_0__Block_byref_n_1

__block 修饰的变量,在内部会被转化为 __Block_byref_xx_xx 结构体,属性分别有:

  • __isa:isa 指针
  • __forwarding:转发对象
  • __flags:标志位
  • __size:大小
  • 对象类型会有 __Block_byref_id_object_copy__Block_byref_id_object_dispose
  • __Block_byref 结构体内和捕获外部变量一样,维护一个内部变量

回到 __main_block_impl_0,其内部维护的变成了 __Block_byref 结构体变量。构造函数在构造的时候,会把 __forwarding 指向外部 __block 对象。Block 内值变动时,直接将 __forwarding 所维护的变量拿出来设置值,以达到在 block 内改变变量值。

Block 的三种类型

Block 有三种类型,分别为:

  • _NSConcreteStackBlock:指用到外部局部变量、成员属性变量,且没有强指针引用的 block。生命周期由系统控制,返回值后即被系统销毁。
  • _NSConcreteMallockBlock:有强指针引用或 copy 修饰的成员属性引用,block 会被复制一份到堆中,没有强指针引用时即销毁。有开发者控制。
  • _NSConcreteGlobalBlock:没用到外界变量或只用到全局变量、静态变量的 block。生命周期为从创建到程序运行结束。

Block 的 copy 和 dispose

copy

copy 内部实现为 _Block_copy,代码如下:

1
2
3
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}

_Block_copy_internal 对应的简化后的实现:

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

内部处理逻辑:

  • 通过 Block 的 descriptor 获取 block size
  • 直接在堆上创建一个新 block
  • 使用 memmove 拷贝内存空间
  • 将 isa 设置为 _NSConcreteMallocBlock
  • 若 block 是 copy 的,调用 aBlock->descriptor->copy

在 block 的 descriptor 中,新增了 2 函数指针:copy 和 dispose。它们会指向 block 捕获变量的 assign 和 retain,这也是 block 会循环引用的原因。

copy 指针最终会调用 _Block_object_assign(...),会根据不同类型,传入不同 flags。

  • 若 block 引用了对象类型,会传入 BLOCK_FIELD_IS_OBJECT。最终会调用 _Block_retain_object 来 retain 对象,使用 _Block_assign 来赋值。
  • 若 block 引用了 block,会传入 BLOCK_FIELD_IS_BLOCK。最终会调用 _Block_copy_internal 来进行 copy
  • 若 block 引用了 __block 变量,会传入 BLOCK_FIELD_IS_BYREF。最终会调用 _Block_byref_assign_copy 来设置 forwarding 指向

dispose

释放过程最终会调用 aBlock->descriptor->dispose,与 copy 过程相对应。

__block 中的 __forwarding

若 block 为 _NSConcreteStackBlock__fordwarding 指向自己。block 内通过 __forwarding->xxx 访问或修改 __block 修饰的对象。

若 block 被 copy,会 malloc 出一片心空间,__block 变量也会被 copy,同时 copy 会设置 __forwarding,这时将 __forwarding 指向堆上 block 的 __forwarding。同样 block 内可以通过 __forwarding->xxx 访问或修改 __block 修饰的对象,区别是在堆上。

总结

  • Block 的结构
  • Block 捕获外部变量
  • __block 实现
  • 三种类型
  • copy
  • dispose
  • _forwarding

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @JonyFang 创建,使用 Stellar 作为主题,您可以在 GitHub 找到本站源码。