这次分享要解决的问题

Block 为什么会引起循环引用

本次实验

  • 平台信息
  • Apple LLVM version 8.0.0 (clang-800.0.42.1)
  • Target: x86_64-apple-darwin16.4.0
  • Thread model: posix
  • InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
  • MRC

为什么会出现循环引用

1
2
3
4
5
6
+-----------+ +-----------+
| instance | | Block |
---> | | --------> | |
| retain 2 | <-------- | retain 1 |
| | | |
+-----------+ +-----------+

如上图所示。两个实例相互持有。
1.如果要释放instance,那么要先释放Block。
2.如果要释放block,那么要先释放instance。
条件1和2不能够同时满足,那么两个都不会被进行释放,这就是所谓的循环引用。

Block的实现

工具Clang(GCC的替代品),编译源码生成可执行文件。
–rewrite-objc将OC的.m文件转换为cpp文件。

1
clang -rewrite-objc Test.m

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
^{
NSLog(@"Hello Block");
}();
}
return 0;
}

利用Clang命令对源码进行重写。提取关键代码。

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

从中我们可以得到些什么关键的数据
一个基础block结构体_block_imp
一个静态C函数_main_block_func_0(命名方式:当前类的函数名+block+func+当前Block在文件中Block的序列号。)
一个block的结构体_main_block_impl_0(命名方式:当前类的函数名+block+impl+当前Block在文件中Block的序列号。)
一个block的解释体main_block_desc_0(命名方式:当前类的函数名+desc+impl+当前Block在文件中Block的序列号。)

简化得到基础的Block结构体模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Block_literal_1 {
void *isa;//ARC:__NSMallocBlock__/__NSGlobalBlock__ MRC:__NSMallocBlock__/__NSStackBlock__/__NSGlobalBlock__
int flags;//copy到堆时候用的标识
int reserved;//保留变量
void (*invoke)(void *, ...);//函数指针,实际Block的函数调用地址,类似于实例方法的IMP
struct Block_descriptor_1 *descriptor;//Block描述信息
};
//非本节关注点。有兴趣自由研究。
struct Block_descriptor_1 {
unsigned long int reserved;//保留变量
unsigned long int size;//Block在内存中的大小
void (*copy)(void *dst, void *src);//copy到堆上使用的方法
void (*dispose)(void *);//执行Release操作
const char *signature;//函数签名
};

  • NSMallocBlock:堆区
  • NSStackBlock :栈区
  • NSGlobalBlock:Data/Text区

Block参数自动捕获机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
NSString *a = nil;
^{
NSLog(@"%@",a);
NSLog(@"Hello Block");
}();
}
return 0;
}

简化上述描述,最后得到结果Block为如下值,从中我们可以看到Block的结构体实例会持有a。

1
2
3
4
5
6
7
8
9
struct Block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
NSString *a;
};

看完了C的这些实现我们来看OC的代码,测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
@interface Test ()
@property (nonatomic, strong) NSString *a;
@end
@implementation Test
- (void)test{
^{
NSLog(@"%@",self.a);
NSLog(@"Hello Block");
}();
}
@end

简化得到Block如下,从中我们看到其实Block是持有self的。

1
2
3
4
5
6
7
8
9
struct Block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
Test *self;
};

所以Block是通过持有self实例来调用a的成员变量
简化版调用a成员变量的实现

1
2
3
4
5
static void __Test__test_block_func_0(struct __Test__test_block_impl_0 *__cself) {
Test *self = __cself->self; // bound by copy
NSLog((NSString *)objc_msgSend)((id)self, sel_registerName("a"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dz_d6n3371d4951v8wz6yx9tbwc0000gn_T_Test_92b943_mi_1);
}

实际使用

retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如:

1
2
3
4
5
6
7
8
9
10
11
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
+-----------+ +-----------+
| request | | Block |
---> | | --------> | |
| retain 2 | <-------- | retain 1 |
| | | |
+-----------+ +-----------+
1
2
3
4
5
6
7
8
9
10
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
+-----------+ +-----------+
| request | | Block |
---->| | --------> | |
| retain 1 | < - - - - | retain 1 |
| | weak | |
+-----------+ +-----------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ClassA* objA = [[[ClassA alloc] init] autorelease];
objA.myBlock = ^{
[self doSomething];
};
self.objA = objA;
+-----------+ +-----------+ +-----------+
| self | | objA | | Block |
| | --------> | | --------> | |
| retain 2 | | retain 1 | | retain 1 |
| | | | | |
+-----------+ +-----------+ +-----------+
^ |
| |
+------------------------------------------------+

扩展知识

__block和__weak实现原理(有兴趣的自行了解)
关键代码

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_byref_weakSelf_0 {
void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
typeof (self) weakSelf;
};
struct __Test__test_block_impl_0 {
struct __block_impl impl;
struct __Test__test_block_desc_0* Desc;
__Block_byref_weakSelf_0 *weakSelf; // by ref
__Block_byref_string_1 *string; // by ref
__Test__test_block_impl_0(void *fp, struct __Test__test_block_desc_0 *desc, __Block_byref_weakSelf_0 *_weakSelf, __Block_byref_string_1 *_string, int flags=0) : weakSelf(_weakSelf->__forwarding), string(_string->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Test__test_block_dispose_0(struct __Test__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Test__test_block_dispose_0(struct __Test__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 8/*BLOCK_FIELD_IS_BYREF*/);}

参考文档

LLVM 中 block 实现源码
谈Objective-C block的实现
文中实际使用举例
NSInvocation<->block