Block
# Block的结构体
下面为oc的一个简单的block:
void block4() {
int outA = 8;
int (^block)(int) = ^(int a) {
return outA + a;
};
outA = 5;
int result = block(3);
NSLog(@"block4 -> result = %d", result);
}
2
3
4
5
6
7
8
9
对其进行clang编译之后
struct __block4_block_impl_0 {
struct __block_impl impl;
struct __block4_block_desc_0* Desc;
int outA;
__block4_block_impl_0(void *fp, struct __block4_block_desc_0 *desc, int _outA, int flags=0) : outA(_outA) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __block4_block_func_0(struct __block4_block_impl_0 *__cself, int a) {
int outA = __cself->outA; // bound by copy
return outA + a;
}
static struct __block4_block_desc_0 {
size_t reserved;
size_t Block_size;
} __block4_block_desc_0_DATA = { 0, sizeof(struct __block4_block_impl_0)};
void block4() {
int outA = 8;
int (*block)(int) = ((int (*)(int))&__block4_block_impl_0((void *)__block4_block_func_0, &__block4_block_desc_0_DATA, outA));
outA = 5;
int result = ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0y_zzzq899s2js3g0f8d71jvbnm0000gp_T_main_2d0f53_mi_3, result);
}
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
block结构体包括以下内容:
- block_impl
- block_desc
- 复制的外部变量
- 构造函数
而真正执行的过程如下:
调用过程void block4, 其中第二句调用__block4_block_impl_0
构造函数初始化结构体:__block4_block_impl_0((void *)__block4_block_func_0, &__block4_block_desc_0_DATA, outA)
得到__block4_block_impl_0
类型变量赋值给block,然后执行block->FuncPtr。事实上,使用clang编译后得到的C代码,发现结构体函数使用_outA初始化结构体中的outA变量,然后再改变outA的值,在真正调用FuncPtr的时候是使用结构体中的outA的值,所以改变outA的值,result仍是11
苹果Block源码地址为:https://opensource.apple.com/source/libclosure/ (opens new window) 选择最新的文件夹查看Block_private.h (opens new window)即可。
主要使用以下几个结构体:
typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);
typedef void(*BlockInvokeFunction)(void *, ...);
typedef void(*BlockByrefKeepFunction)(struct Block_byref*, struct Block_byref*);
typedef void(*BlockByrefDestroyFunction)(struct Block_byref *);
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
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
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#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
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#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_descriptor_small {
uint32_t size;
int32_t signature;
int32_t layout;
/* copy & dispose are optional, only access them if
Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
int32_t copy;
int32_t dispose;
};
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
struct Block_byref {
void * __ptrauth_objc_isa_pointer 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
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
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
网上找了一张老版本的图:
# block_impl
也就是Block_layout
结构体,包括内容
- isa 表示block是一个对象,就是
_NSConcreteStackBlock
,_NSConcreteMallocBlock
,_NSConcreteGlobalBlock
这几个 - Flags 描述block对象,Block_private.h (opens new window)中定义的一个枚举值
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 标识栈Block
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
BLOCK_NEEDS_FREE = (1 << 24), // runtime 标识堆Block
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 含有copy_dispose助手
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler 是否为全局Block
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- FuncPtr(invoke) 函数指针block_func,封装block代码的函数
- Reserved 保留位
# block_desc
记录block的大小、copy和dispose方法
- reserve
- size
- copy
- dispose
# block_byref
当变量被__block修饰符修饰的时候,编译后会生成,结构体如下:
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler 表示含有layout
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler 表示byref中含有copy_dispose函数,在__block捕获的变量为对象时就会生成copy_dispose函数用来管理对象内存
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime 判断是否要释放
};
struct Block_byref {
void * __ptrauth_objc_isa_pointer 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
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
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
结构和Block_layout
类似,
- isa 指针
- forwarding 在栈中指向自己,Block执行copy后指向堆中的byref。
- flags 枚举,runtime中会用到
- size 占用内存大小
# 变量捕捉
所谓的捕获外部变量,就是在block内部创建外部对应的变量。
全局变量整个项目都可以使用,所以block不用担心变量被释放,使用的时候直接访问。
局部变量有作用域,如果block调用的时候已经被释放,则会出现问题,所以需要捕获,在block内部创建对应的变量。
# 全局变量
不捕获,也就是内部不创建对应的变量。可以直接访问改变全局变量
# 局部变量
auto变量: 捕获,block结构体中创建对应的变量,将外部的值赋值给block内部对应的变量,属于值传递,block内部存在的是block内部创建的变量,所以在block内部不能够修改block外部变量的内容。(外部对象变量,传递的是变量是指针的地址,即栈中的地址,所以也是不可以改变的)
静态变量: 捕获,指针传递,因为静态变量的存储区域也在全局存储区,但可能有多个名称相同的静态变量存在,所以需要用地址访问。block内部可以修改静态变量的值
# Block的类型
- __NSGlobalBlock__block没有访问auto变量,内存区域为数据段
- __NSStackBlock__block访问了auto变量,内存区域为栈
- __NSMallocBlock__block调用了copy方法,内存区域为堆
# Block的复制操作
# 类型变化
- NSGlobalBlock 调用copy后什么都不做
- NSStackBlock 调用copy后从栈复制到堆,副本存储位置为堆
- NSMallocBlock 调用copy后引用计数加一,副本存储位置为堆
# 自动copy情况
- Block作为函数返回时
- 将block赋值给strong指针时
- Block作为cocoa api中方法名包含usingBlock的方法参数时,比如数组遍历
- Block作为GCD api中方法参数时,比如:dispatch_onece, dispatch_after,执行完block后系统才对block执行release
# 为什么block要从栈拷贝到堆?
因为栈中的对象的内存空间为系统管理,可能随时被释放,而堆中的为开发者自己管理,当block在栈中时,可能存在在调用block的时候,block本身已经被释放,从而出现错误。
# __block修饰符
__block修饰符用来使外部auto变量能够在block内部进行修改。__block不能修饰全局变量和静态变量。__block修饰的变量,在block内部会转换成__Block_byref的结构体,我修改的只是对象中的一个属性,而不是修改这个对象:
- isa
- __Block_byref: forwarding,存储的是__Block_byref的地址,指向自己
- flag
- size
- 变量
数据结构参考block_byref介绍
block在修改外部变量的时候是通过__Block_byref结构体的forwarding读取和修改的量
# forwarding的作用
为什么要通过forwarding转一下,而不是直接读取?
因为从栈中复制到堆中时,__Block_byref的地址发生改变,而通过forwarding转一下之后栈中__Block_byref的forwarding指向堆中的__Block_byref,堆中的forwarding还指向自己,从而保证访问__Block_byref中变量是同一份
# block的内存管理
- 当block在栈上时,不会对指向的对象产生强引用
- 当block从栈上复制到堆上时,会调用block内部的copy函数,copy函数会调用__Block_object_assign函数,会根据所指对象的修饰符(__strong,__weak)做出响应的操作,形成强引用或者弱引用。
- 当block从堆上移除时,会调用block内部的dispose函数,dispose函数会调用__Block_object_dispose函数,函数会自动释放所指对象
# 循环引用问题
- 用__weak解决,这样block就不强持有外部变量了
- 用__block解决,block执行时将__block变量设置为nil,这要求block必须被执行。
# UIViewAnimationBlock
UIView动画不需要考虑循环引用问题。
CoreAnimation相关知识:
- UIView层
- Layer层
- data数据层
其中UIView层的block仅仅是提供了类似快照data的变化。当真正执行Animation动画时才会将“原有状态”与“执行完block的状态”做一个差值,来去做动画
# NSNotificationCenterBlock
不会循环引用,但会发生内存泄漏
[[NSNotificationCenter defaultCenter] addObserverForName:@“someNotification” object: nil queue:[NSOperationQueue mainQueue] usingBlock^(NSNotification *notification) {
self.someProperty = xyz;
}];
2
3
NSNotificationCenter单例生成并持有一个Observation,持有了弱引用self和强持有block, 但block持有self。但self并没有持有block和observation,所以不存在循环引用。由于self未持有方法返回的Observer,所以无法主动移除Observer,单例强持有了block,block持有了self,self不会被释放。
注意:此种方法返回的对象被系统强持有,self需要持有方法返回的对象,用于停止通知移除观察者。如果不持有这个对象,是无法彻底销毁这个通知的。
所以block一定要弱持有self,self要持有返回的observer,在delloc中移除通知
# NSNotificationCenterIVABlock
会循环引用,会发生内存泄漏
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@“someNotification” object: nil queue:[NSOperationQueue mainQueue] usingBlock^(NSNotification *notification) {
self.someProperty = xyz;
}];
2
3
NotificationCenter的observer强持有block,block持有self,self持有_observer形成循环引用。
# GCDBlock
不会循环引用,不会发生内存泄漏
dispatch_group_async(self.operationGroup, self.serialQueue, ^{
[self doSomething];
});
2
3
self确实持有了queue,而block也确实持有了self。但并没有证据或者文档表面queue一定会持有block,而且即使queue持有了block,在block执行完毕的时候,由于需要从任务对列中移除,因此完全可以解除queue对block的持有关系,所以实际上这里也不存在循环引用。
# NSOperationQueueBlock
不会循环引用,不会发生内存泄漏
[[NSOperationQueue mainQueue] addOperationWithBlock^{
self.someProperty = xyz;
}];
2
3
mainQueue持有了block,block持有了self,self并没有持有mainQueue,所以不存在循环引用[NSOperationQueue mainQueue]
并非单例,所以并没有内存泄漏
# block可以给NSMutableArray中添加元素吗?需不需使用__block?
答案:可以添加元素,不需要__blcok
修饰符
因为block内部仅仅使用了NSMutableArray的内存地址,往其中添加元素时并没有修改NSMutableArray的地址,因此可以添加,且不用__block
修饰符