我们在使用 block 的时候,如果在 block 中使用 self 有可能会循环引用,产生内存泄漏的问题。
通常,我们如果遇到这种情况,我们会将 self 转换成 weak automatic 的变量,这样就避免了 block 对self 强引用,即:
__weak typeof(self) weakSelf = self;
__weak __typeof__(self) weakSelf = self;
__weak __typeof(self) weakSelf = self;
以上三种写法,任选一种都是可以的,typeof 就是获取变量的类型,没别的深奥的东西。
但是,我在查看 AFNetworking 源码的时候发现发现,在有的 block 中使用self,作者并没有将 self 转化weakSelf,难道这样不会引起内存泄漏的问题吗?
为此,我做了相关的测试。我们知道如果存在内存泄漏,那么 dealloc 方法就不会被调用。
测试一:
@property (copy, nonatomic) dispatch_block_t testBlock;
- (void)viewDidLoad {
[super viewDidLoad];
self.testBlock = ^(){
NSLog(@"%@", [self class]);
};
self.testBlock();
}
这块我为了简单,直接使用 GCD 中的
typedef void (^dispatch_block_t)(void)
它是一个无参数,无返回值的 block。
这个 testBlock 是控制器(以下用 VC 代替)的一个属性。在 block 中直接使用self就会造成循环引用,Xcode也会做出相应的警告提示:
⚠️Capturingselfstrongly in this block is likely to lead to a retain cycle.
这句话的意思就是说,此处的 block 强引用了 self,会存在保留环,即循环引用,那么 VC 的 dealloc 方法也不会被正常调用。这个时候我们就需要将其转化为 weakSelf 来打破这个保留环,避免内存泄漏。代码更正如下:
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.testBlock = ^(){
NSLog(@"%@", [weakSelf class]);
};
self.testBlock();
}
这样VC的 dealloc 方法也可以正常被调用了。
结论:当 block 直接做为 VC 的属性时,如果 block 内部没有使用 weakSelf,则会造成循环引用,导致内存泄漏。
测试二:
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.testBlock = ^(){
[weakSelf doSomething];
};
self.testBlock();
}
- (void)doSomething {
NSLog(@"%@", [self class]);
}
测试发现 VC 的 dealloc 方法被正常调用。
结论:当在 block 中调用一个方法,并且这个方法中直接或者间接的使用 self,不会出现内存泄漏的问题。
测试三:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_block_t block = ^(){
[self doSomething];
};
block();
}
- (void)doSomething {
NSLog(@"%@", [self class]);
}
测试发现 VC 的 dealloc 方法被正常调用,所以我们在使用 GCD 的时候,大部分情况都不需要做转换。
结论:当 block 不是 self 的属性时,block 内存使用 self不会造成内存泄漏的问题。
测试四:
- (void)viewDidLoad {
[super viewDidLoad];
Class VC = self.class;
[VC doSomethingWithBlock:^{
[self doSomething];
}];
}
+ (void)doSomethingWithBlock:(dispatch_block_t)block {
if (block) {
block();
}
}
- (void)doSomething {
NSLog(@"%@", [self class]);
}
测试发现 VC 的 dealloc 方法被正常调用,我们在使用 UIView 有关动画的类方法时,大部分情况都不需要做转换。
结论:当使用类方法,并且类方法中用 block 做参数时,block 内部使用 self也不会造成内存泄漏的问题。
通过这么多的测试,我们可以看到,当且仅当 block 直接或间接的被 self 持有时,如果不做 weakSelf 转换,就会有内存泄漏的风险。
最后补充一点,同样在查看AFNetworking的时候,遇到有时候还需要转化成 strongSelf 的情况:
__weak __typeof(self) weakSelf = self;
NSURLSessionDataTask *dataTask = nil;
dataTask = [self.sessionManager GET:request.URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
sf_dispatch_main_async_safely(^{
if (success) {
success((NSHTTPURLResponse *)task.response, responseObject);
}
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:task.currentRequest.URL];
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
});
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
sf_dispatch_main_async_safely(^{
if (failure) {
failure(error);
}
});
}];
那么什么情况下,需要将 weakSelf 转化成 strongSelf 呢 ?
由于 __weak 变量的特殊性,会在对象销毁后自动置为 nil,如果在 block 中多次需要访问 self,就需要转化为 strong automatic,确保在 block 使用期间,self 不会被释放。