深入探讨Block循环引用

循环引用由来

俩个对象相互强引用,俩个对象的retainCount一直无法为0,内存无法释放造成内存泄漏;
多个对象同理;

一. __strong __weak实现原理

所有权修饰:符:__strong,__weak,__unsafe_unretained,__autoreleasing;
所有权修饰符默认不写是__strong

__strong示例

对象持有自己
生成的时候用alloc/new/copy/mutableCopy

声明一个__strong对象

1
2
3
{
id __strong obj = [[NSObject alloc] init];
}

LLVM编译器转换为

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

简单的调用

1
2
3
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);

由于在ARC下,会自动插入release代码,所以不难理解

对象不持有自己
生成的时候不使用alloc/new/copy/mutableCopy
例如使用类方法

1
2
3
{
id __strong obj = [NSArray array];
}

LLVM编译器转换为

1
id __attribute__((objc_ownership(strong))) array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));

简单的调用

1
2
3
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

相比之前的对象持有自己的情况不同,此处多出了一个objc_retainAutoreleasedReturnValue函数

此处有三个函数需要说明(文档的8.13,8.14,8.15)

1.id objc_retainAutoRelease(id value)

  • 描述

    If value is null, this call has no effect. Otherwise, it performs a retain operation followed by an autorelease operation.

  • 实现

    id objc_retainAutorelease(id value) {return objc_autorelease(objc_retain(value));}

2.id objc_retainAutoReleaseReturnValue(id value)

  • 描述

    If value is null, this call has no effect. Otherwise, it performs a retain operation followed by the operation described in objc_autoreleaseReturnValue

  • 实现

    id objc_retainAutoreleaseReturnValue(id value) {return objc_autoreleaseReturnValue(objc_retain(value));}

3.id objc_retainAutoReleasedReturnValue(id value)

  • 描述

    If value is null, this call has no effect. Otherwise, it attempts to accept a hand off of a retain count from a call to objc_autoreleaseReturnValue on value in a recently-called function or something it calls. If that fails, it performs a retain operation exactly like objc_retain.

    这个函数用于自己持有对象的函数(retain),它持有的对象返回注册在autoreleasepool中的对象非的方法或者是函数的返回值

ARC中原本对象的生成是要注册到autoreleasepool中,但是调用了id objc_autoReleaseReturnValue(id value),又紧接着调用了id objc_retainAutoReleasedReturnValue(id value),那么id objc_retainAutoReleasedReturnValue(id value)会去坚持该函数的方法或者函数调用方法的执行命令列表.如果有id objc_retainAutoReleasedReturnValue(id value)方法,那么对象就直接返回给方法或者函数的调用方,达到了对象不注册到autoreleasepool,也拿到了相应的对象

__weak示例

声明一个__weak对象

1
2
3
{
id __weak obj = strongObj;
}

LLVM编译器转换为

1
id __attribute__((objc_ownership(none))) obj1 = strongObj;

简单的调用

1
2
3
id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);

此时我们去文档查看objc_initWeak(8.7)

1
2
3
4
id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}

先将传入的obj变为nil,然后执行objc_storeWeak函数,再看objc_destoryWeak函数(8.6)

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

也是去调用objc_storeWeak唯一不同的是传参不同,一个是nil,一个是value,接下来不用多说,看objc_storeWeak函数(8.18)

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.
如果value是空指针或它指向的对象已开始解除分配,则将对象指定为null并取消注册为weak对象。 否则,将对象注册为weak对象或将其注册更新为指向值。

objc_storeWeak作用很明显了.weak表是一个Hash table,来维护weak修饰的指针变量,objc_storeWeak传入第一个变量作为Key注册到weak表中,再根据第二个参数决定是否移除.如果第二个参数为0,那么把__weak变量从weak表中删除记录,并从引用计数表中删除对应的键值记录

二. weakSelf strongSelf用法

众所周知,打破循环引用使用__weak来修饰self,例如

__weak __typeof(self)weakSelf = self,这是AFN里面的写法
__weak typeof(self) weakSelf = self,这是我们平时的写法

那么 __typeoftypeof有什么区别呢?其实两者是一样的,只不过兼容的问题 区别详细资料

那么 strongSelf是用来干嘛的呢,相信看过AFNetworking等一些第三方源码的会发现这种strong weak dance很常见,在block里面,如果有一些延迟操作里面用到了weakSelf,很有可能在用到之前它就已经释放了

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
#import <Foundation/Foundation.h>
typedef void(^ExampleBlock)(void);
@interface Person : NSObject
@property(nonatomic,copy) ExampleBlock exampleBlock;
@property (nonatomic, copy)NSString *name;
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
person.name = @"hello world";
__weak typeof(person) weakSelf = person;
person.exampleBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
person.exampleBlock();
}

打印结果: null

exampleBlock()这个block执行结束后,person这个实例化的对象由于没有强指针指向,出了{}作用域就释放的了;当dispatch_after这个函数再次捕获__weak修饰的person对象的时候,由于原对象已经释放,所以会打出null

这时候__strong就派上用场了,如下

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
#import <Foundation/Foundation.h>
typedef void(^ExampleBlock)(void);
@interface Person : NSObject
@property(nonatomic,copy) ExampleBlock exampleBlock;
@property (nonatomic, copy)NSString *name;
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
person.name = @"hello world";
__weak typeof(person) weakSelf = person;
person.exampleBlock = ^{
__strong __typeof(person) strongSelf = weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
person.exampleBlock();
}
> 打印结果: hello world

三. @weakify @strongify实现原理

这俩个宏是RAC避免循环引用的宏,虽然不用RAC用不到,不过我们还是可以看下它们的实现过程

@weakify

1
2
3
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

@strongify

1
2
3
4
5
6
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")

根本毫无头绪是不是,再接着往下看
rac_keywordify

1
2
3
4
5
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

我们发现在debug模式下使用了@autoreleasepool,这是为了维持编译器的分析能力;而使用@try/@catch是为了防止插入一些不必要的autoreleasepool;所以基本可以认为rac_keywordify时间上就是autoreleasepool{}的宏替换,再加上前面的@,形成了@autoreleasepool{}
再看weakify的第二行

metamacro_foreach_cxt(racweakify,, weak, VA_ARGS__)

1
2
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

先看metamacro_concat

1
2
3
4
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B

经过套入后

1
2
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_foreach_cxt##metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

__VA_ARGS__是可变参数,获取...中传入的N个参数

再看 metamacro_argcount(__VA_ARGS__)

1
2
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

这个宏的作用是用来获取参数的个数

metamacro_at

1
2
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)

实现过程

拼接metamacro_at ## N(传入的第一个值,20)(VA_ARGS):

1
metamacro_at20(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

metamacro_at20它的实现:

1
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)

截取前20个数,剩下的传入metamacro_head

metamacro_head定义:

1
2
3
4
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST

metamacro_head的作用是返回第一个参数.例如@weakify(self):

1
metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

截取完后,就会变成metamacro_head_(1),返回1.

再回到最初的套入##metamacro_foreach_cxt:

1
metamacro_foreach_cxt##metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

接下来再套入:

1
metamacro_foreach_cxt##N(MACRO, SEP, CONTEXT, __VA_ARGS__)

接着套入刚才的@weakify(self)的例子:

1
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, __VA_ARGS__)
1
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

此时在传入之前的三个参数

1
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

传入后

1
metamacro_foreach_cxt1(rac_weakify_, , __weak, self) rac_weakify_(0,__weak,self)

N = 20

1
2
3
4
#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
SEP \
MACRO(19, CONTEXT, _19)

类似递归,先rac_weakify_(0,__weak,_19),然后把前19个数传入 metamacro_foreach_cxt19,metamacro_foreach_cxt19rac_weakify_(0,__weak,_18),然后把前18个数传入metamacro_foreach_cxt18 …直到 metamacro_foreach_cxt1.当N=0,不错任何操作

1
#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)

再看rac_weakify_

1
2
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

INDEX顾名思义是第几个的意思,只是一个标记,没有实际作用.例如@weakify(self):rac_weakify_(0,__weak,self)

1
__weak __typeof__ (self) self_weak_ = self;

self_weak_就是弱化之后的self,这就是@weakify(self)的实现原理;

@strongify是加了一些警告,实现原理基本上是一样的;

参考

Objective-C Automatic Reference Counting (ARC) — Clang 7 documentation
剖析@weakify、@strongify - 推酷
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用 - IOS - 伯乐在线

这个人很帅<br>他什么都不想说<br>