探究 block 数据结构及内存管理

本文首先将介绍一些 block 的基础, 随后着重介绍下面的内容

  • block 的数据结构
  • block 的内存管理(retain,release)

会用到下面这个
可编译的源码

基础

语法

Block 的语法比较难记, 以至于出现了 fuckingblocksyntax 这样的网站专门用于记录 block 的语法, 摘录如下:

作为变量

1
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

作为属性

1
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

作为函数声明参数

1
- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

作为函数调用中的参数

1
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

作为 typedef

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

捕获外部变量

block 可以捕获来自外部作用域的变量(id 类型, C++类型, 基础数据类型, block), 这是 block 一个很强大的特性

1
2
3
4
5
6
7
- (void)foo {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
testBlock();
}

正常情况下, 捕获的外部变量在 block 里做的修改,在外部是不起作用的。如果想要在外部起作用,需要使用 __block 来声明变量:

1
int __block anInteger = 42

所以,根据变量是否被 __block 修饰,可以将变量分为两类:

  • by ref: 引用类型。该类的变量被 __block 修饰,在 block 内部对其修改,外部也生效
  • by copy:拷贝类型。该类的变量不被 __block 修饰,在 block 内部对其修改,外部不生效(全局/静态 变量除外)

至于原因进阶部分会进行详细的探究

进阶

数据结构

运行下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef void(^BLK)(void);

- (void)foo
{
static int staticInt = 5;
int ret = 5;

BLK globalBlock = ^{
int a = staticInt;
};
BLK mallocBlock = ^{
int a = ret;
};

NSLog(@"global = %@", globalBlock);
NSLog(@"malloc = %@", mallocBlock);
NSLog(@"stack = %@", ^{int a = ret;});
}

打印结果为

1
2
3
global = <__NSGlobalBlock__: 0x104bb22b0>
malloc = <__NSMallocBlock__: 0x281a21bf0>
stack = <__NSStackBlock__: 0x16b8266d8>

在 iOS 平台中, 一共有三种类型的 block:

  • _NSConcreteGlobalBlock: 在 .data 区域, block 内部没有访问任何的外部非(静态变量 && 全局变量)的变量(^{;}同样是该类型)
  • _NSConcreteMallocBlock: 在堆中创建内存, 使用__strong修饰的 block
  • _NSConcreteStackBlock: 在栈中创建内存, 使用__weak修饰的 block 或者是匿名 block

优先级为 _NSConcreteGlobalBlock > _NSConcreteMallocBlock == _NSConcreteStackBlock,即满足 _NSConcreteGlobalBlock 条件的 block 就是 _NSConcreteGlobalBlock 类型的

_NSConcreteGlobalBlock 类型的 block 我不知道初始化的时候是否直接在 .data 区域创建。
_NSConcreteMallocBlock 和 _NSConcreteStackBlock类型的 block 在初始化时在栈中创建,随后如果有 __storng 强指针引用的话,
则进行 retain 操作,将其内存拷贝到堆中,后续的 retain 操作则只是增加 block 的引用计数


使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 YOUR_FILE_NAME将下面的代码转换成 C++ 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)foo
{
static int staticInt = 5;
NSObject *commonObject = [[NSObject alloc] init];
__weak NSObject *weakObject = commonObject;
__block NSObject *byrefObject = [[NSObject alloc] init];
__block __weak NSObject *byrefWeakObject = commonObject;
BLK blockObject = ^{
NSObject *val = commonObject;
};

BLK malocBlock = ^{
staticInt++;
NSLog(@"%@", commonObject);
NSLog(@"%@", byrefWeakObject);
NSLog(@"%@", weakObject);
NSLog(@"%@", byrefObject);
NSLog(@"%@", byrefWeakObject);
NSLog(@"%@", blockObject);
};
NSLog(@"malloc = %@", malocBlock);
}

转换得到的 C++ 实现, 只截取部分

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __Block_byref_byrefObject_0 {
void *__isa;
__Block_byref_byrefObject_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong byrefObject;
};
struct __Block_byref_byrefWeakObject_1 {
void *__isa;
__Block_byref_byrefWeakObject_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__weak byrefWeakObject;
};

struct __TestObject__foo_block_impl_0 {
struct __block_impl impl;
struct __TestObject__foo_block_desc_0* Desc;
NSObject *__strong commonObject;
__TestObject__foo_block_impl_0(void *fp, struct __TestObject__foo_block_desc_0 *desc, NSObject *__strong _commonObject, int flags=0) : commonObject(_commonObject) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestObject__foo_block_func_0(struct __TestObject__foo_block_impl_0 *__cself) {
NSObject *__strong commonObject = __cself->commonObject; // bound by copy

NSObject *val = commonObject;
}
static void __TestObject__foo_block_copy_0(struct __TestObject__foo_block_impl_0*dst, struct __TestObject__foo_block_impl_0*src) {_Block_object_assign((void*)&dst->commonObject, (void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __TestObject__foo_block_dispose_0(struct __TestObject__foo_block_impl_0*src) {_Block_object_dispose((void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __TestObject__foo_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestObject__foo_block_impl_0*, struct __TestObject__foo_block_impl_0*);
void (*dispose)(struct __TestObject__foo_block_impl_0*);
} __TestObject__foo_block_desc_0_DATA = { 0, sizeof(struct __TestObject__foo_block_impl_0), __TestObject__foo_block_copy_0, __TestObject__foo_block_dispose_0};

struct __TestObject__foo_block_impl_1 {
struct __block_impl impl;
struct __TestObject__foo_block_desc_1* Desc;
int *staticInt;
NSObject *__strong commonObject;
NSObject *__weak weakObject;
__strong BLK blockObject;
__Block_byref_byrefWeakObject_1 *byrefWeakObject; // by ref
__Block_byref_byrefObject_0 *byrefObject; // by ref
__TestObject__foo_block_impl_1(void *fp, struct __TestObject__foo_block_desc_1 *desc, int *_staticInt, NSObject *__strong _commonObject, NSObject *__weak _weakObject, __strong BLK _blockObject, __Block_byref_byrefWeakObject_1 *_byrefWeakObject, __Block_byref_byrefObject_0 *_byrefObject, int flags=0) : staticInt(_staticInt), commonObject(_commonObject), weakObject(_weakObject), blockObject(_blockObject), byrefWeakObject(_byrefWeakObject->__forwarding), byrefObject(_byrefObject->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestObject__foo_block_func_1(struct __TestObject__foo_block_impl_1 *__cself) {
__Block_byref_byrefWeakObject_1 *byrefWeakObject = __cself->byrefWeakObject; // bound by ref
__Block_byref_byrefObject_0 *byrefObject = __cself->byrefObject; // bound by ref
int *staticInt = __cself->staticInt; // bound by copy
NSObject *__strong commonObject = __cself->commonObject; // bound by copy
NSObject *__weak weakObject = __cself->weakObject; // bound by copy
__strong BLK blockObject = __cself->blockObject; // bound by copy

(*staticInt)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_0, commonObject);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_1, (byrefWeakObject->__forwarding->byrefWeakObject));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_2, weakObject);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_3, (byrefObject->__forwarding->byrefObject));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_4, (byrefWeakObject->__forwarding->byrefWeakObject));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_5, blockObject);
}
static void __TestObject__foo_block_copy_1(struct __TestObject__foo_block_impl_1*dst, struct __TestObject__foo_block_impl_1*src) {_Block_object_assign((void*)&dst->commonObject, (void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->byrefWeakObject, (void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->byrefObject, (void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->blockObject, (void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static void __TestObject__foo_block_dispose_1(struct __TestObject__foo_block_impl_1*src) {_Block_object_dispose((void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static struct __TestObject__foo_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestObject__foo_block_impl_1*, struct __TestObject__foo_block_impl_1*);
void (*dispose)(struct __TestObject__foo_block_impl_1*);
} __TestObject__foo_block_desc_1_DATA = { 0, sizeof(struct __TestObject__foo_block_impl_1), __TestObject__foo_block_copy_1, __TestObject__foo_block_dispose_1};

static void _I_TestObject_foo(TestObject * self, SEL _cmd) {
static int staticInt = 5;
NSObject *commonObject = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(weak))) NSObject *weakObject = commonObject;
__attribute__((__blocks__(byref))) __Block_byref_byrefObject_0 byrefObject = {(void*)0,(__Block_byref_byrefObject_0 *)&byrefObject, 33554432, sizeof(__Block_byref_byrefObject_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"))};
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_byrefWeakObject_1 byrefWeakObject = {(void*)0,(__Block_byref_byrefWeakObject_1 *)&byrefWeakObject, 33554432, sizeof(__Block_byref_byrefWeakObject_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, commonObject};
BLK blockObject = ((void (*)())&__TestObject__foo_block_impl_0((void *)__TestObject__foo_block_func_0, &__TestObject__foo_block_desc_0_DATA, commonObject, 570425344));

BLK malocBlock = ((void (*)())&__TestObject__foo_block_impl_1((void *)__TestObject__foo_block_func_1, &__TestObject__foo_block_desc_1_DATA, &staticInt, commonObject, weakObject, blockObject, (__Block_byref_byrefWeakObject_1 *)&byrefWeakObject, (__Block_byref_byrefObject_0 *)&byrefObject, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h0_ybj03b8d0mj52n8dx82v2br40000gn_T_TestObject_1a9b07_mi_6, malocBlock);
}

需要注意的是,上面生成的 C++ 代码中 block 的结构体,并不符合最新版本 objc4-779.1 源码里 block 的结构定义

在 objc4-779.1 里 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
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};

不是有三种类型的 BLOCK_DESCRIPTOR 结构体,只是根据功能将其分为三部分,其实是一个整体
注意下面两个结构体在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SampleA {
int a;
int b;
int c;
};

struct SampleB {
int a;
struct Part1 {
int b;
};
struct Part2 {
int c;
};
};

Block_layout 成员变量介绍:

  • void *isa: isa 指针
  • int flags: 使用位域来保存信息, 例如引用计数, 是否正在被销毁等信息
  • int reserved: 保留变量
  • void (*invoke)(void *, …): 函数指针, 指向 block 实现函数的调用地址
  • struct Block_descriptor *descriptor: block 的附加描述信息,一般来说都包含 Block_descriptor_1,但是是否包含 Block_descriptor_2 和 Block_descriptor_3 需要根据捕获外部变量的类型来判断
  • 还有一些捕获的外部变量
位域名 位置 含义
BLOCK_DEALLOCATING 0x0001 1 表示正在被销毁
BLOCK_REFCOUNT_MASK 0xfffe block 是引用计数
BLOCK_NEEDS_FREE 1 << 24 1 表示 block 已拷贝到堆中
BLOCK_HAS_COPY_DISPOSE 1 << 25 block 是否有 copy/dispos 函数,即 descriptor 是否包含 Block_descriptor_2
BLOCK_HAS_CTOR 1 << 26 copy/dispose helper 函数里面有 C++代码
BLOCK_IS_GC 1 << 27 1 表示使用 GC 管理内存,iOS 平台中不使用 GC
BLOCK_IS_GLOBAL 1 << 28 1 表示是个全局 block
BLOCK_USE_STRET 1 << 29 arm64 架构下没用,不知道干嘛的
BLOCK_HAS_SIGNATURE 1 << 30 是否有函数类型编码
BLOCK_HAS_EXTENDED_LAYOUT 1 << 31 GC 下使用

Block_descriptor 成员变量介绍:

  • unsigned long int reserved: 预留变量
  • unsigned long int size: block 结构体的 size 大小
  • void (*copy)(void *dst, void *src): copy 函数,将 block 成员变量 从栈拷贝到堆中。后面会再介绍
  • void (*dispose)(void *): dispose 函数, 对 block 成员变量内存回收
  • const char *signature:函数的类型编码
  • const char *layout: GC 下使用,不知道具体作用

让我们对照着 C++ 实现捋一遍,因为实现里的 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 __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __TestObject__foo_block_impl_1 {
struct __block_impl impl;
struct __TestObject__foo_block_desc_1* Desc;
int *staticInt;
NSObject *__strong commonObject;
NSObject *__weak weakObject;
__strong BLK blockObject;
__Block_byref_byrefWeakObject_1 *byrefWeakObject; // by ref
__Block_byref_byrefObject_0 *byrefObject; // by ref
__TestObject__foo_block_impl_1(void *fp, struct __TestObject__foo_block_desc_1 *desc, int *_staticInt, NSObject *__strong _commonObject, NSObject *__weak _weakObject, __strong BLK _blockObject, __Block_byref_byrefWeakObject_1 *_byrefWeakObject, __Block_byref_byrefObject_0 *_byrefObject, int flags=0) : staticInt(_staticInt), commonObject(_commonObject), weakObject(_weakObject), blockObject(_blockObject), byrefWeakObject(_byrefWeakObject->__forwarding), byrefObject(_byrefObject->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

在结构体 __TestObject__foo_block_impl_1 有很多捕获的外部变量充当的成员变量,如下所示

1
2
3
4
5
6
int *staticInt;
NSObject *__strong commonObject;
NSObject *__weak weakObject;
__strong BLK blockObject;
__Block_byref_byrefWeakObject_1 *byrefWeakObject; // by ref
__Block_byref_byrefObject_0 *byrefObject; // by ref

为了尽可能的谈论多的情况,在示例代码中我在 block 加了许多不同类型的外部变量
可以看到,全局/静态 变量和 __block 变量,都是将变量的地址保存在成员变量中,这样做的目的是为了在内部修改该变量在外部也会生效。
而其它非 __block 变量则仅仅拷贝了值,类似于浅拷贝

__Block_byref_byrefWeakObject_1 是 Block_byref 类型的结构体。__block 变量在编译时变成对应的 Block_byref 实例,且实例持有该变量

Block_byref 结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};

struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
};

struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};

类似 Block_descriptor,根据功能将其分为三部分
成员变量介绍:

  • void *isa:一般指向 0x0,如果该变量还被 __weak 修饰,则指向 _NSConcreteWeakBlockVariable
  • struct Block_byref *forwarding:指向该结构体。在拷贝到堆的过程中,在堆中新建一个结构体实例,此时栈中的实例并没有被销毁,将栈中实例 forwarding 指向堆中的实例
  • int flags:引用计数使用的 bit 数目和位置与 block 相同,其它不再介绍
  • int size:Block_byref 结构体的字节长度
  • void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src):Block_byref 的 copy 函数,帮助将实例持有的变量拷贝到堆中
  • void (*byref_destroy)(struct Block_byref *):Block_byref 的 dispose 函数,帮助将持有的变量销毁
  • const char *layout:Block_byref 持有的变量

为了方便,后面将 Block_layout 的 copy/dispose 函数简称为 Block copy/dispose 函数; Block_byref 的 byref_keep/byref_destroy 函数简称为 __block copy/dispose 函数

foo() 函数中我们在 block 捕获了两个 __block 修饰的变量,下面是其中一个的 Block_byref 结构体

1
2
3
4
5
6
7
8
9
struct __Block_byref_byrefObject_0 {
void *__isa;
__Block_byref_byrefObject_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong byrefObject;
};

编译时,编译器会将 __block 修饰的变量 byrefObject,转换成对应的 Block_byref 结构体 __Block_byref_byrefObject_0 实例,然后让实例持有该变量NSObject *__strong byrefObject;


block 结构体 __TestObject__foo_block_impl_1 里面还定义了一个初始化方法:

1
2
3
4
5
6
_TestObject__foo_block_impl_1(void *fp, struct __TestObject__foo_block_desc_1 *desc, int *_staticInt, NSObject *__strong _commonObject, NSObject *__weak _weakObject, __strong BLK _blockObject, __Block_byref_byrefWeakObject_1 *_byrefWeakObject, __Block_byref_byrefObject_0 *_byrefObject, int flags=0) : staticInt(_staticInt), commonObject(_commonObject), weakObject(_weakObject), blockObject(_blockObject), byrefWeakObject(_byrefWeakObject->__forwarding), byrefObject(_byrefObject->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
  • 其中类似 : commonObject(_commonObject) 的写法是将形参NSObject *__strong _commonObject赋值给成员变量 _commonObject
  • 参数 flags 有一个默认值 0, 在本例中传入的值为 570425344, 用二进制表示为 0b00100010000000000000000000000000,即第 30 位(BLOCK_USE_STRET), 第 26 位(BLOCK_HAS_COPY_DISPOSE) bit 的值 1
  • 参数 fp 是函数指针, 在本例中它的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
static void __TestObject__foo_block_func_1(struct __TestObject__foo_block_impl_1 *__cself) {
__Block_byref_byrefWeakObject_1 *byrefWeakObject = __cself->byrefWeakObject; // bound by ref
__Block_byref_byrefObject_0 *byrefObject = __cself->byrefObject; // bound by ref
int *staticInt = __cself->staticInt; // bound by copy
NSObject *__strong commonObject = __cself->commonObject; // bound by copy
NSObject *__weak weakObject = __cself->weakObject; // bound by copy
__strong BLK blockObject = __cself->blockObject; // bound by copy

(*staticInt)++;

// 省略。。。
}

也就是 block 里面的代码。
可以看到非 __block 变量后面都写了注释 bound by copy,__block 变量后面的注释是 bound by ref,__block 变量都会转换成 Block_byref 实例保存在 block 中
为了方便,后面将 __block 修饰的变量称为引用变量,否则称为拷贝变量

  • block 初始化参数 desc 的类型是__TestObject__foo_block_desc_1 *

结构体的定义如下:

1
2
3
4
5
6
static struct __TestObject__foo_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestObject__foo_block_impl_1*, struct __TestObject__foo_block_impl_1*);
void (*dispose)(struct __TestObject__foo_block_impl_1*);
} __TestObject__foo_block_desc_1_DATA = { 0, sizeof(struct __TestObject__foo_block_impl_1), __TestObject__foo_block_copy_1, __TestObject__foo_block_dispose_1};

并且初始化了一个结构体实例 __TestObject__foo_block_desc_1_DATA,其中 reserved 的值为 0, Block_size 的值为结构体 __TestObject__foo_block_impl_1 的字节长度
copy 和 dispose 两个函数指针分别指向函数 __TestObject__foo_block_copy_1 和 __TestObject__foo_block_dispose_1
实现如下:

1
2
3
static void __TestObject__foo_block_copy_1(struct __TestObject__foo_block_impl_1*dst, struct __TestObject__foo_block_impl_1*src) {_Block_object_assign((void*)&dst->commonObject, (void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->byrefWeakObject, (void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->byrefObject, (void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->blockObject, (void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static void __TestObject__foo_block_dispose_1(struct __TestObject__foo_block_impl_1*src) {_Block_object_dispose((void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);}

这两个函数很容易看懂,有多少捕获的外部变量,就调用多少次 _Block_object_assign()_Block_object_dispose() 函数。
需要注意的是 _Block_object_assign() 的第三个参数,根据变量的类型不同传入不同的标记,后面会详细讲

与其类似的是 Block_byref 的 copy/dispose 函数
在本例中它们的实现如下:

1
2
3
4
5
6
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

可以看到,Block_byref 的 copy/dispose 函数最终也是调用 _Block_object_assign/_Block_object_dispose 函数

至于参数为什么要强转成 char*, 我的理解是这样的:

举个例子,定义一个 int 类型的数组, int a[10]。我们可以使用指针来代替数组的下标, 例如用(int *)a + 1来表示数组的第二个元素, 其距离第一个元素偏移了 4 个字节长度,所以强制转换成 char* 类型是为了每次偏移 1 个字节。上面的代码表示偏移了 40 个字节

而老版本 Block_byref 的定义(因为 C++ 实现符合源码)

1
2
3
4
5
6
7
8
9
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
int flags; /* refcount; */
int size;
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
/* long shared[0]; */
};

Block_byref 偏移 40 个字节后的位置刚好是持有变量的首地址,所以在这里传入的参数即是引用变量(被 __block 修饰的变量)
至于后面的数字 131 后面再讲

好了,数据结构就分析到这啦。
目前我们可以知道的是,block 以及 __block 变量在编译时会生成对应的 Block_layout,Block_byref 结构体,它们都有各自的 copy/dispose 函数

验证 block 数据结构

这一节主要使用 lldb 用来验证 block 的数据结构

打个断点,使用命令 x/8xg ptr

block 内部分布

根据上一节 block 结构体内容,我们可以知道各成员变量的值

block 成员变量
void *isa 0x00000001ca53c0f0
volatile int32_t flags 0xc3000002
int32_t reserved 0x0
void (*invoke)(void *, …) 0x0000000104bcce80
struct Block_descriptor_1 *descriptor 0x000000010519e290
捕获变量 _commonObject 0x0000000281eaac10
Block_byref * _byrefObject 0x0000000281225170

验证一下:

isa 验证

isa 指针指向 __NSMallocBlock__,没问题

flags 用二进制表示为 0b11000011000000000000000000000010,即位域 BLOCK_HAS_SIGNATURE,BLOCK_HAS_EXTENDED_LAYOUT,BLOCK_HAS_COPY_DISPOSE,BLOCK_NEEDS_FREE 为 1 引用计数 BLOCK_REFCOUNT_MASK 为 1

invoke 验证
可以通过上面的方法打印出函数指针的内容

成员变量 _commonObject 的值于 commonObject 相同,均为 0x0000000281eaac10,说明是浅拷贝

下面来验证 descriptor
0x000000010519e290 为 Block_descriptor_1 结构体的首地址

descriptor 内存分布

Block_descriptor 成员变量 解释
uintptr_t reserved 0x0 预留字段
uintptr_t size 0x0000000000000030 十进制为 48,即 block 结构体的字节长度
void (*copy)(void *dst, const void *src) 0x0000000104bccee8 copy 函数指针
void (*dispose)(const void *) 0x0000000104bccf54 dispose 函数指针
const char *signature 0x000000010511847f 函数类型编码
const char *layout 0x0000000000000110 不知道干嘛的

打印下 block 函数的类型编码

函数类型编码

成员变量 const char *layout 应该是在 GC 下使用的,具体作用不明白


接下来验证 Block_byref 的结构

Block_byref 内存分布

Block_byref 成员变量 解释
void *isa 0x0 isa
struct Block_byref *forwarding 0x0000000281225170 十进制为 48,即 block 结构体的字节长度
volatile int32_t flags 0x33000004 标记,二级制表示为 0b00110011000000000000000000000100
uint32_t size 0x00000030 Block_byref 结构体长度
byref_keep 0x000000010511847f __block copy 函数指针
byref_destroy 0x0000000000000110 __block dispose 函数指针
const char *layout 0x0000000281eaac50 持有的变量 byrefObject

flags 表示引用计数为 2,因为初始化有一个,然后 block 有一个。位域 BLOCK_BYREF_LAYOUT_STRONG 为 1,表示该变量是 __strong 类型
const char *layout 表示其持有的变量

如何将 block 从栈拷贝到堆中

现在我们来探究一下 block 是如何从栈中拷贝到堆中的吧。
除 global block 类型的 block 均在栈中创建,当被强引用,即 retain block 的话,block 就会从栈拷贝到堆中,如果已经在堆中,则增加其引用计数

断点调试

step into

_Block_copy 的函数实现在 clang-800 源码 中可以看到

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

_Block_copy_internal 实现

我们主要看红色框框里面的代码:

  • 首先通过 malloc() 在堆中新建一个 block 结构体实例,接着使用memmove()将旧实例的数据拷贝过去
  • 重置新实例成员变量 flags 的 BLOCK_REFCOUNT_MASK(引用计数)部分
  • 将新实例成员变量 flags 的位域 BLOCK_NEEDS_FREE 设置为 1, 表示该 block 在堆中
  • 将新实例的 isa 指向 _NSConcreteMallocBlock
  • 如果存在 Block copy 函数,则调用

上一节中已经提到过,这里再贴一下它的实现:

1
2
3
4
5
6
7
8
static void __TestObject__foo_block_copy_1(struct __TestObject__foo_block_impl_1*dst, struct __TestObject__foo_block_impl_1*src) 
{
_Block_object_assign((void*)&dst->commonObject, (void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->byrefWeakObject, (void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->byrefObject, (void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->blockObject, (void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);
}

需要注意的是,如果外部变量是 C++类型,则不会调用 _Block_object_assign()函数,而是其对应的 const 拷贝构造方法。注释如下:
In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers. The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign. The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.

_Block_object_assign函数的实现如下:

_Block_object_assign 函数实现

参数 flags 有以下几种情况:

由 Block copy 函数调用:

  • id                   3       
    
    • (^Block)             7
      
  • __block 8
    • __weak __block       8+16
      

flags 有 4 种可能: 3, 7, 8, (8+16)

由 __block copy 函数调用:

BLOCK_BYREF_CALLER (128):表示由 __block copy 调用
此时, 传入的 flags 有 4 种可能:

  • __block id                   128+3       
    
    • __block (^Block)             128+7
      
  • __weak __block id 128+3+16
    • __weak __block (^Block)      128+7+16
      

总共有以上 8 种情况

需要注意的是,Block copy 调用该函数,第一个参数是指针的地址(void **),第二个参数传入的是指针的值(void *)
而 __block copy 调用该函数,传入的前两个参数均为指针的值(void *)

下面根据 case 条件分几步来讲解这个函数:

  • 代码块 1:

Block copy 函数调用,且该变量是 id 类型的

代码_Block_retain_object()最终会调用下面这个函数

1
2
3
static void _Block_retain_object_default(const void *ptr) {
(void)ptr;
}

代码_Block_assign()最终会调用下面这个函数

1
2
3
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}

不知道调用 _Block_retain_object 函数的目的是什么
这一分支仅做了浅拷贝,拷贝指针内容

  • 代码块 2:

Block copy 函数调用,且该变量也是一个 block

这里首先调用了 _Block_copy_internal() 函数, 先将 block 类型的变量拷贝到堆中
然后调用 _Block_assign 将其堆中的地址赋值给 Block 对应的成员变量

  • 代码块 3:

Block copy 函数调用,且变量被 __block 修饰

_Block_byref_assign_copy 实现

这里传入的第一个参数 void *dest 表示的是指针的地址

_Block_byref_assign_copy 实现中代码块 1 的逻辑如下

  • 声明一个布尔值 isWeak, 用来表示该变量是否还被 __weak 修饰
  • _Block_allocator 函数最终调用 malloc() 函数, 在堆上拷贝一份同样内存大小的 Block_byrefs 实例
  • 将旧实例的成员变量 flags 拷贝到新实例中, 并将新实例的引用计数设置为 2。 1 份是因为栈上有一个实例, 1 份是因为堆上也有一个, 1+1 就等于 2 了
  • 将旧实例和新实例的成员变量 forwarding 均赋值为新实例
  • 如果 isWeak 为 true, 则将 Block_byrefs 实例的 isa 指针指向 _NSConcreteWeakBlockVariable
  • 如果实例有 __block copy/dispose helpers(还是调用 _Block_object_assign 函数), 则调用它对实例持有的变量进行拷贝到堆操作; 如果没有的话则将旧实例中 size 后面的成员变量拷贝到新实例的 bit 中

函数里面有一行看起来比较让人困扰的代码

1
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);

在前面的声明中,src 被声明成了 Block_byref * 类型,所以 src + 1 的意思是从 src 的首地址偏移 sizeof(Block_byref) 个字节,即 Block_byref_2 的首地址
结构体拷贝完成后,随后将 Block_byref 持有的变量通过函数 *src2->byref_keep 也拷贝到堆中
_Block_byref_assign_copy 实现中代码块 2 的作用是如果 Block_byref 持有的变量已经拷贝到堆中了, 则增加其引用计数

  • 代码块 4:

__block copy 函数调用,且持有的变量不被 __weak 修饰

最终会调用下面那个函数

1
2
3
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}

浅拷贝

  • 代码块 5:

__block copy 函数调用,且持有的变量被 __weak 修饰

最终会调用下面那个函数:

1
2
3
static void _Block_assign_weak_default(const void *ptr, void *dest) {
*(void **)dest = (void *)ptr;
}

这里讲一下我的理解:
void * 是一个指针,里面保存了一个地址,但是我们不能 *(void *) 这样使用它,因为我们不知道它指向的结构是什么类型的。如果要使用的话就需要将其转换成其它类型,例如 int *,所以在这里可以仅仅把它看成是变量,保存了一个地址。
而 void **,其表示指向指针的指针,不同于 void *,我们可以 *(void **) 这样使用它,因为我们知道 void ** 指向的内容是一个指针。

在 _Block_assign_weak_default 函数中,我们先将 dest 强转成 void ** 类型,然后就可以对其进行赋值操作啦


至此,整个拷贝流程已经讲的差不多了,这里总结一下:

  • 一个 block 可能捕获多个外部变量
  • block 在栈中生成,retain 后,将栈中的内容拷贝到堆中
  • block 会调用 Block copy 函数,对其捕获的变量也进行拷贝操作
    • 如果是 C++ 类型,则调用其 const 拷贝构造函数到堆中
    • 如果是 block 类型,则将其拷贝到堆中
    • 如果是 id 类型,因为已经在堆中了,所以进行浅拷贝,仅复制指针值
    • 如果是 __block 修饰的变量,则将其对应的 Block_byref 结构体拷贝到堆中,随后调用 __block copy 函数将其持有的变量也拷贝到堆中

如何销毁 block

这一节我们将探究如何销毁 block

创建一个 malloc block 类型的 block,foo() 结束,block 会被回收。
在函数尾巴那里打个断点

step into

将断点停留在objc_release()函数
runtime 通过该函数对 block 进行 release 操作,如果其引用计数变成 0,则销毁

继续 step into

block 是特殊的类,自己重写了release()函数,所以代码 ISA()->hasCustomRR() 返回的结果是 true,将执行自己重写的 release() 函数
继续 step into

因为没有 block 的 release()源码,通过调用栈发现随后调用了 _Block_release() 函数
可编译的源码中可以找到该函数的实现(直接在文件夹中搜索)

_Block_release 实现

根据 if 的判断将函数分为三个部分

  • 判断条件 1:如果是全局 block 或者其引用计数已经为 0(表示已经在销毁了), 则返回
  • 判断条件 2:如果使用 GC 管理内存,则执行什么什么操作。因为 iOS 平台不使用 GC,所以略过
  • 判断条件 3:如果 block 已经在堆上了,则将其引用计数减 1,如果减为 0,则调用下面三个函数
1
2
3
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
_Block_deallocator(aBlock);
  • 第一个函数 _Block_call_dispose_helper

在同个文件中搜索该函数,其实现如下

1
2
3
4
5
6
7
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;

(*desc->dispose)(aBlock);
}

如果存在 Block dispose 函数,则调用

1
2
3
4
5
6
7
8
9
static void __TestObject__foo_block_dispose_1(struct __TestObject__foo_block_impl_1*src) 
{
_Block_object_dispose((void*)src->byrefInt, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->commonObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->byrefWeakObject, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->byrefObject, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->blockObject, 7/*BLOCK_FIELD_IS_BLOCK*/);
}

_Block_object_dispose 实现

通过判断条件将 _Block_object_dispose 分为几部分

  • 判断条件 1:变量由 __block 修饰

_Block_byref_release 实现

代码byref = byref->forwarding;,因为可能会有 byref 在栈中,而 forwarding 此时却在堆中的情况。随后判断 byref 是否在栈中,如果是的话则立即返回。
对 Block_byref 的引用计数减 1 随后判断是否为 0,如果是的话则调用其 byref_destroy 函数(也就是 _Block_object_dispose),销毁其持有的变量
最后将 Block_byref 结构体的内存释放掉

  • 判断条件 2:变量是 Block 类型

_Block_destroy 实现

调用 _Block_release 来销毁该 block

  • 判断条件 3:变量是 id 类型

_Block_release_object 最终调用的函数如下

1
2
3
static void _Block_release_object_default(const void *ptr) {
(void)ptr;
}

等于什么都没做,这是因为 id 类型的对象由 ARC 管理其内存。即不再被强指针引用时引用计数减 1

  • 判断条件 4

什么都没做,如果走到走一步说明可能系统有异常


接着讲 block 销毁时调用的第二个函数 _Block_destructInstance
调试时,调用栈里也有这个函数

接着 step into

使用该函数,主要是为了将在弱引用表中注册的使用 __weak 引用 block 的变量置为 nil,因为 block 已经要被销毁了
这里不仔细讲了


最后调用 _Block_deallocator() 函数,将 block 结构体的内存销毁


这里总结一下 block 的内存销毁流程:

  • 先将 block 捕获的外部变量进行销毁
  • 将弱引用 block 的指针置为 nil
  • 将 block 结构体的内存销毁

谈block、__weak和__strong

最近在”翻新”公司的老项目的时候,发现一个奇怪的问题:

在一个 block 中,我使用了 RAC 为了避免 block 循环引用而定义的两个宏: @weakify@strongify,但是如果在 block 内部使用下划线属性(成员变量),还是会导致循环引用。

很多人都知道怎么处理这个问题,在使用了@weakify@strongify的情况下,在 block 内部像self -> ivar这样使用成员变量就可以避免循环引用了,但是为什么这样用就没问题呢?使用了@weakify@strongify两个宏之后发生了什么呢?带着你在使用 block 时出现过的疑问,在后面的内容中你可能会得到答案。

block是什么

block 是用于创建匿名函数的 C 语言扩展。用户使用 block 指针与 block 对象进行交互并传输 block 对象,block 指针表示为普通指针。block 可以从局部变量中捕获值;发生这种情况时,必须动态分配内存。初始分配在栈上完成,但 runtime 提供了一个Block_copy函数,给定一个 block 指针,将底层 block 对象复制到堆中,将其引用计数设置为1并返回新的 block 指针,或者(如果 block 对象已经在堆上)将其引用计数增加1.配对函数是Block_release,它将引用计数减少1并在计数达到零并且在堆上时销毁对象。翻译自苹果文档

上面的翻译来自于 谷歌翻译~。我对于 block 的理解就是一个指针,指向一个带有函数指针 (用于执行block内的代码) 的结构体,该结构体内有许多捕获的成员变量。在 ARC 环境下 block 会从 栈中自动复制到堆中,方便 runtime 管理内存生命周期;如果内部有全局变量则复制到数据区,生命周期为程序创建到程序结束。

[站外图片上传中…(image-1d1a52-1561035272667)]

block的数据结构

block 的数据结构定义如下
[站外图片上传中…(image-f5b34b-1561035272667)]

结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};

struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;// sizeof(struct Block_layout)
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};

通过它的数据结构,我们知道一个 block 实际上是由5部分组成的

  1. isa 指针,所有对象都有该指针,用于实现对象相关的功能
  2. flags,用于按 bit 位表示一些 block 的附加信息
  3. reserved,保留变量
  4. invoke,函数指针,指向具体的 block 实现的函数调用地址
  5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针

block的几种的类型

常见的 block 有下面三种,不用类型的 block 存放不同的区域,在 ARC 环境下只有_NSConcreteGlobalBlock_NSConcreteMallocBlock两种类型的 block

  • _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
  • _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
  • _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。

下面是详细的介绍

_NSConcreteStackBlock

该类型的 block 仅存在在 MRC 环境中,数据存放在栈区,当函数返回时会被销毁。在 ARC 环境中,不存在_NSConcreteStackBlock类型,只存在_NSConcreteGlobalBlock_NSConcreteMallocBlock 两个类型。在下面的例子中, block 的类型的打印结果是__NSMallocBlock__。原因可能是因为c语言的结构体中,编译器不能很好地管理初始化和销毁,这样对内存管理来说很不方便,所以就将 block 放到堆上,使用 runtime 来管理它们的生命周期。

1
2
3
4
5
6
7
8
9
10
int val = 1;

void(^textBlock)(void) = ^{
NSLog(@"[block] val<%p>: %d", &val, val);
NSLog(@"val: %d", val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"textBlock: %@", textBlock);

打印结果为:

1
2
3
4
val<0x16b523d1c>: 1
[block] val<0x280a9fcb0>: 1
textBlock: <__NSMallocBlock__: 0x28076a4c0>

下面使用 clang -rewrite-objc filename 将代码转换成 C++ 的实现, 下面是关键部分的代码

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
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
int val;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy

}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
static int static_v = 1;
int val = 1;

void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, val));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);

}

  • 其中__MyObject__test_block_impl_0 是 block 的结构体类型
  • __MyObject__test_block_func_0 是 block 实现的函数,在 __MyObject__test_block_impl_0内有一个指针FuncPtr 指向该函数
  • __MyObject__test_block_desc_0 是 block 附件描述信息的结构体,包含着 block 结构体大小, copy 和 dispose 函数指针(这两个函数后面后讲到)等的描述信息,在 __MyObject__test_block_impl_0内有一个指针Desc 指向该结构体
  • _I_MyObject_test 函数内可以看到 block 的初始化,void(*textBlock)(void) 说明 textBlock 是一个指向该 block 结构体的指针

首先观察这个__MyObject__test_block_impl_0的结构体:

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

  • 使用 clang 转换过的实现是 MRC 环境的,所以 isa 指针指向 _NSConcreteStackBlock 类型
  • 在这个结构体中可以看到一个成员变量int val; ,没错,它就是 block 捕获的局部变量,从构造函数 __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) 中可以看到,block 仅仅捕获了该变量的值
  • __MyObject__test_block_impl_0中由于增加了一个变量 val,所以结构体的大小变大了,结构体大小被写在了__MyObject__test_block_desc_0
  • block 捕获外部变量仅仅只 block 闭包里面会用到的值,其他用不到的值,它并不会去捕获。

再看一下__MyObject__test_block_func_0这个函数的实现:

1
2
3
4
5
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy

}

我们可以发现,系统自动给我们加上的注释,bound by copy,自动变量 val 虽然被捕获进来了,但是是用 __cself->val 来访问的。block 仅仅捕获了 val 的值,并没有捕获 val 的内存地址。所以在__MyObject__test_block_func_0 这个函数中即使我们重写这个自动变量 val 的值,依旧没法去改变block外面变量 val 的值。

小结一下:
基本数据类型的变量是以值传递方式传递到 block 的构造函数里面去的。block 只捕获 block 中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以 block 内部不能改变变量的值。

_NSConcreteMallocBlock

修改一下上面的代码:

1
2
3
4
5
6
7
8
9
10
 __block int val = 1;
void(^textBlock)(void) = ^{
val++;
NSLog(@"[block] val<%p>: %d", &val, val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"val<%p>: %d", &val, val);
NSLog(@"textBlock: %@", textBlock);

打印输出为:

1
2
3
4
5
val<0x282db0858>: 1
[block] val<0x282db0858>: 2
val<0x282db0858>: 2
textBlock: <__NSMallocBlock__: 0x2823d3450>

重新用 clang 生成的c++实现

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
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};

struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref

(val->__forwarding->val)++;
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}

在重新生成的代码中,我们看到新增了一个名为__Block_byref_val_0的结构体,它是用来替代我们__block修饰的变量 val 的。

  • 它的第一个指针是 isa,说明它也是一个对象。
  • 第二个指针是指向自身类的指针__forwarding
  • 第三个是一个标记 flag
  • 第四个是结构体的大小
  • 第五个是变量 val 的值

在函数static void _I_MyObject_test(MyObject * self, SEL _cmd)我们可以看到该结构体的初始化代码:

1
2
3
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};


  • 在初始化时, isa 指向了一个空指针
  • __forwarding指向了自己的地址
  • 1是变量 val 的值。

使用 __block修饰的变量,无论是基本数据类型还是 OC 的类,在编译之后都是转换成一个新的结构体,该结构体的__forwarding指针会指向自己的地址,而成员变量 val 则为编译前的类型和值。至于这样的目的是什么,可以接着看下面。


1
2
3
4
5
6
7
static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

__MyObject__test_block_desc_0 这个结构体中,我们发现比之前的代码多了一个 copydispose 的函数指针。在c语言的结构体中,编译器没有很好地进行初始化和销毁,这样对内存管理来说很不方便,所以就在增加了这两个函数指针,方便进行内存管理。copy函数把block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。

  • copydispose这两个函数指针对应的两个函数实现
1
2
3
4
5
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}


  • __MyObject__test_block_copy_0函数实现中出现了方法_Block_object_assign,
  • __MyObject__test_block_dispose_0函数实现中出现了方法_Block_object_dispose

下面是这两个方法的申明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

下面是这两个方法的实现:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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;
}

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

  • _Block_copy_internalBlock_copy的一个实现,实现了从_NSConcreteStackBlock复制到_NSConcreteMallocBlock的过程,有9个步骤。

    • 在第8步中我们可以看到 isa 指针指向了_NSConcreteMallocBlock
  • _Block_releaseBlock_release的一个实现,实现了一个block释放的过程,有6个步骤


扯的有点远了,现在让我们总结一下 __block 修饰的变量在block内发生了什么。

  • block 会在栈中被创建,然后通过Block_copy函数复制到堆中。由 runtime 管理它的生命周期
  • 使用 __block 修饰的变量,在编译后会变成一个新的对象。在初始化时,成员变量__forwarding 会指向栈中该变量的地址,val 为该变量原本的值。当 block 的成员变量 __Block_byref_val_0 从栈中复制到堆中时,成员变量 __Block_byref_val_0的地址可能改变了,但是 __forwarding 指针指向的结构体是不会变的,仍然在栈中。
  • block 的实现函数__MyObject__test_block_func_0,block 通过 __Block_byref_val_0 *val = __cself->val;(val->__forwarding->val)++ 变量的地址修改 val,所以在 block 内部修改变量 val 是会影响到 block 外部的变量。
  • 这就是为什么 block 内部和外部 val 的地址不同的原因(一个在栈上,一个在堆上)。因为他们__forwarding指向的结构体是一样的,所以在 block 内部修改变量会影响到外部,

_NSConcreteGlobalBlock

block 内部只用到全局变量,包括全局变量静态全局变量静态变量,以及上述 block 的 copy 版本。数据存放在数据区,生命周期从应用创建到应用结束。

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
int global_v = 1;
static int static_global_v = 1;

@implementation MyObject

- (void)test
{
static int static_v = 1;

NSLog(@"val<%p>: %d", &static_v, static_v);
NSLog(@"global_v<%p>: %d", &global_v, global_v);
NSLog(@"static_global_v<%p>: %d", &static_global_v, static_global_v);

void(^textBlock)(void) = ^{
static_v++;
global_v++;
static_global_v++;
NSLog(@"[block] val<%p>: %d", &static_v, static_v);
NSLog(@"[block] global_v<%p>: %d", &global_v, global_v);
NSLog(@"[block] static_global_v<%p>: %d", &static_global_v, static_global_v);
};
textBlock();
NSLog(@"textBlock: %@", textBlock);
}

打印信息为:

1
2
3
4
5
6
7
8
9
10
val<0x1034b8114>: 1
global_v<0x1034b8110>: 1
static_global_v<0x1034b8118>: 1

[block] val<0x1034b8114>: 2
[block] global_v<0x1034b8110>: 2
[block] static_global_v<0x1034b8118>: 2

textBlock: <__NSGlobalBlock__: 0x10343da40>

clang 之后 C++ 实现:

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
int global_v = 1;
static int static_global_v = 1;


struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
int *static_v;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int *_static_v, int flags=0) : static_v(_static_v) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
int *static_v = __cself->static_v; // bound by copy

(*static_v)++;
global_v++;
static_global_v++;
}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
static int static_v = 1;
}

  • block 仅仅捕获了静态变量 static_v 的地址作为自己的成员变量,因此在内部修改该变量可以影响到 block 外部。block 内部和外部该变量的地址相等
  • 全局变量 global_v 和全局静态变量 static_global_v 并没有被 block 捕获,因为他们已经被保存在数据区中,可以直接使用

由于 clang 改写的方式跟 LLVM 不太一样,在这里并没有开启ARC,所以这里我们看到 isa 指向的还是 _NSConcreteStackBlock,但在开启ARC的时候,block 应该是 _NSConcreteGlobalBlock 类型。

block 与 self

在前面的部分,我们已经分析过 局部变量,静态变量,全局变量,全局静态变量在 block 时的情况,那么,还有一种特殊的变量 self,它在 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
@interface MyObject () {
NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@end

@implementation MyObject

- (void)test
{

self.name = @"n";
_age = @"10";
NSLog(@"self: %@", self);

void(^textBlock)(void) = ^{
self.name = @"a";
_age = @"11";

NSLog(@"[block] self: %@", self);
NSLog(@"[block] name<%p>: %@", self.name, self.name);
NSLog(@"[block] age<%p>: %@", _age, _age);
};
NSLog(@"name<%p>: %@", self.name, self.name);
NSLog(@"age<%p>: %@", _age, _age);
textBlock();
NSLog(@"name<%p>: %@", self.name, self.name);
NSLog(@"age<%p>: %@", _age, _age);
}

打印结果:

1
2
3
4
5
6
7
name<0x102804818>: n
age<0x102804838>: 10
[block] name<0x102804858>: a
[block] age<0x102804878>: 11
name<0x102804858>: a
age<0x102804878>: 11

clang之后的C++实现

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
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *self = __cself->self; // bound by copy

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;

}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_0);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_1;

void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}

  • __MyObject__test_block_impl_0中我们可以看到self也被 block 捕获成了成员变量
  • __MyObject__test_block_impl_0的构造函数中我们可以看到 self 被当做参数被传入,而不是 self 的地址
  • 因为 block 在内部和外部 self 指向的是相同的 MyObject 结构体,所以在 block 内部对 self 成员变量进行修改会影响到 block 外部
  • block 的结构体会强引用 self,所以需要小心使用,否则会引起循环应用
1
2
3
4
5
6
7
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *self = __cself->self; // bound by copy

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;
}

block 内部使用属性和成员变量是不一样的。直接使用属性时,走的是 obj_msgSend 消息发送(具体可以研究这篇博客),而在使用成员变量时,应该是先通过 self 得到结构体的首地址,然后通过成员变量的偏移量然直接使用这个成员变量(其实我也没很理解。。。)

小结一下:

  • block 内部使用 self 时的情况跟使用局部变量的情况是比较类似的,block 会捕获 self 的值而不是地址当做成员变量
  • 在 block 内部使用属性和成员变量的情况是不一样的

__weak与__strong

我们都知道使用__weak和__strong修饰符可以避免在block的使用中出现循环引用的问题,这是为什么呢?先让我们了解一下这两个修饰符吧!

ARC 环境下,OC的对象面前都需要加上所有权的修饰符,所有的修饰符有以下4种

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

默认的修饰符是__strong。

ARC下,self既不是strong也不是weak,而是unsafe_unretained的,也就是说,入参的self被表示为:(init系列方法的self除外)来源:博客

1
2
3
4
- (void)start {
const __unsafe_unretained MyObject *self;
}

想要弄清__weak与__strong的实现原理,需要研究一下clang中关于ARC的文档,有兴趣可以点进去仔细看看。

__strong

1
2
id __strong object = [[NSObject alloc] init];

在终端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m转换成 C++ 的实现

1
2
id __attribute__((objc_ownership(strong))) object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

= 右边的代码意思应该是对 NSObject 这个类发送 alloc 消息,然后再对生成的对象发送 init 消息,这两个方法的实现可以在 runtime 中找到,代码我也贴到下面了
= 左边的代码,我不大理解objc_ownership这个函数,查了下搜不到是啥意思,看字面意思应该是两个对象间的持有关系,也就是自己持有自己的意思。

1
2
3
4
5
6
7
8
9
+ alloc
{
return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}
- init
{
return self;
}

__weak

1
2
3
id __strong object = [[NSObject alloc] init];
id __weak weakSelf = object;

在终端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m转换成 C++ 实现

1
2
3
id __attribute__((objc_ownership(strong))) object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) weakSelf = object;

相应的会调用

1
2
3
objc_initWeak(&weakSelf,object);
objc_destoryWeak(&weakSelf);

objc_initWeak方法的文档说明
Precondition: object is a valid pointer which has not been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is zero-initialized. Otherwise, object is registered as a __weak object pointing to value. Equivalent to the following code:

id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}

这个函数会把传入的 object 置为nil,然后执行objc_storeWeak函数。


那么objc_storeWeak函数是干什么的呢?下面是这个方法的说明

Precondition: object is a valid pointer which either contains a null pointer or has been registered as a __weak object. value is null or a pointer to a valid object.
If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value.
Returns the value of object after the call.

objc_storeWeak函数的用途就很明显了。由于weak表也是用Hash table实现的,所以objc_storeWeak函数就把第一个入参的变量地址注册到weak表中,然后根据第二个入参来决定是否移除。如果第二个参数为0,那么就把__weak变量从weak表中删除记录,并从引用计数表中删除对应的键值记录

所以如果__weak引用的原对象如果被释放了,那么对应的__weak对象就会被指为nil。原来就是通过objc_storeWeak函数这些函数来实现的。


接下来是 objc_destoryWeak 函数的实现

1
2
3
4
void objc_destroyWeak(id *object) { 
objc_storeWeak(object, nil);
}

还是调用上面的objc_storeWeak函数,因为传入的value为nil,所以object将从weak表中删除并且置为nil

__weak与__strong的作用

终于讲到这两个所有权修饰符的作用了。


首先是不使用这两个修饰符时的情况。在上面我们已经讲到过 block 存在 self 的一种情况了,下面我们要讲一下 block 存在 self 并且 self 强应用 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
@interface MyObject ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject
- (void)test
{
self.textBlock = ^{
self.name = @"n";
}
}

@end

@implementation OneViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.object = [[MyObject alloc] init];
[self.object test];
}


对于 MyObject 来说是造成了循环引用的,因为它强引用了 block,而 block 内部也强引用着 self,所以 MyObject 是不能被dealloc的,但奇怪的是,将 MyObject 当做属性的 OneViewController 竟然可以dealloc,这估计是另一个问题了,等我有空再去研究一下这个。。。

使用 clang 得到的C++实现,这边只截取了block结构体和初始化block部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 初始化
((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344)

在这个部分中可以看到 block 将 self(MyObject *指针)捕获成了自己的成员变量了(强引用), 而self指针的成员变量又包含block,造成循环引用。


仅仅使用__weak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject

- (void)test
{
__weak typeof(self) weakSelf = self;
self.textBlock = ^{
weakSelf.name = @"n";
NSLog(@"hh");
};
self.textBlock();
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m得到C++实现

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
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak weakSelf;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)weakSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_970d18_mi_0);
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}

苹果使用一个全局的 weak 表来保存所有的 weak 引用。并将对象作为键,weak_entry_t 作为值。weak_entry_t 中保存了所有指向该对象的 weak 指针。当被指向的对象执行 dealloc 时候,将所有指向该对象的 weak 指针的设置为nil。

  • block 将 __weak 修饰的 self 捕获为成员变量
  • 当 self 执行dealloc时,block 内的 self 置为nil,从而打破循环引用
  • 当 self delloac 之后,在调用 block 的函数指针,block 内部的self置为nil。

同时使用__weak与__strong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface MyObject ()
//{
// NSString *_age;
//}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject

- (void)test
{
__weak typeof(self) weakSelf = self;
self.textBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"n";
NSLog(@"hh");
};
self.textBlock();
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m得到C++实现

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
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak weakSelf;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy

__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)strongSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_0010b9_mi_0);
}
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}

  • __MyObject__test_block_impl_0 block 仍然是将 __weak 修饰的 self 捕获为成员变量
  • 当 self 执行dealloc时,block 内的 self 会被置为nil,从而打破循环引用
  • block 内的代码在__MyObject__test_block_func_0函数内,当使用strongSelf时,会先取出__weak修饰的成员变量self:MyObject *const __weak weakSelf = __cself->weakSelf;, 然后再生成一个__strong修饰的局部变量__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;,self 的引用计数 +1。这样的目的是在 block 内的代码块执行完之前避免 self 被dealloc掉。当 block 执行完毕之后,局部变量 strongSelf 被释放,self 的引用计数 -1。

@weakify 和 @strongify

这两个是RAC中避免Block循环引用而开发的2个宏,实现过程很牛,值得我们学习。限于篇幅,我就不分析了,想了解可以点开这篇博客
这两个宏展开下来就相当于:

1
2
3
4
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;


回到开头

好了,不知道你看了这么多头晕了没有。。。下面让我们回到开头我碰到的那个问题,为什么我使用了 @weakify 和 @strongify,然后直接使用下划线的成员变量还是会造成循环引用。原因就是_ivar直接使用成员变量,self 跟 weakSelf会同时被 block 捕获成 block 的成员变量,注意:self 还是会被 block 捕获的(前面好像没写例子,不过你可以自己写写看),导致 block 还是强引用着 self,导致循环引用。解决办法就是 strongSelf -> ivar这样使用成员变量

总结

  • block 会捕捉 block 内部的变量

    • 当变量类型是局部变量(基本数据类型时或 oc 类),仅捕获该变量的值,所以 block 内部和外部这两个变量的地址是不一样的,在block 内部修改变量的值也不会影响 block 外部的变量
    • 当变量是 self 时的情况跟 局部变量时是差不多的
    • 当变量类型是__block修饰的布局变量(基本数据类型或者 oc 类),会新构建一个结构体,其中成员变量__forwarding会指向栈中该变量的地址,因此在 block 内部修改该变量会影响 block 外部的变量
    • 当变量是全局变量或者全局静态变量时,block 不会捕获该变量,因为变量已经存在在数据区,可以直接调用。此时 block 也保存在数据区
    • 当变量是静态变量时,block 会捕获该变量的地址,因此在 block 内部修改该变量会影响 block 外部的变量
  • block 结构体中的成员变量 descriptor 包含着 copydispose 两个函数指针。copy 函数把 block 从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。

  • 苹果使用一个全局的 weak 表来保存所有的 weak 引用。并将对象作为键,weak_entry_t 作为值。weak_entry_t 中保存了所有指向该对象的 weak 指针。当被指向的对象执行 dealloc 时候,将所有指向该对象的 weak 指针的设置为nil。

  • 在 block 外部使用 __weak 的原因是,让 block 将这个 __weak修饰的变量捕获成自己的成员变量,这样当外面的变量被 dealloc 后,block 内的该成员变量也将置为 nil,避免循环引用

  • 在 block 里面使用的 __strong 修饰的 weakSelf 是为了在函数生命周期中防止 self 提前释放。strongSelf是一个局部变量,当block内的代码执行完毕就会释放,不会对 self 进行一直进行强引用。

引用

ARC对self的内存管理
深入研究 Block 捕获外部变量和 __block 实现原理
深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用
谈Objective-C block的实现