快年底了,想对一些之前有点模糊的概念进行总结,所以写了总结笔记。主要参考了这几篇文章:
Block技术中的weak-strong
什么时候在 block 中不需要使用 weakSelf
为什么 weakSelf 需要配合 strong self 使用
block 什么时候需要构造循环引用
一.循环引用例子
#import "ViewController.h"
@interface ViewController(){
id _observer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%@", self);
}];
}
- (void)dealloc {
if (_observer) {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
}
在这段代码中,我们向通知中心注册了一个观察者,然后在dealloc
时,解除该注册,看起来很正常,但这里存在循环引用。
a.消息通知block引用了self,这里self对象被block保留一次
b._observer又retain该block的一份拷贝,通知中心又持有_observer.
c.只要_observer对象还没有被解除注册,block就会被通知中心一直持有,从而self就不会被释放,dealloc也不会被调用
d.但我们有希望在dealloc中通过removeObserver来解除注册以消除通知中心对_observer/block的保留次数。
e.同时_observer是self所在类中定义赋值,被self retain
f.总结:因为self要想调用dealloc就必须等通知中心移除注册,释放掉_observer,但是要想通知中心移除注册、释放掉_observer就必须调用dealloc.这样相互等待就变成了死循环。这里即便self不持有_observer也会产生循环引用问题,_observer只是为了告诉通知中心移除哪个注册。
二、解决方法:
1.通过weakSelf和strongSelf
@interface ViewController(){
id _observer;
}
@end
@implementation ViewController
#pragma mark --- life circle
- (void)viewDidLoad {
[super viewDidLoad];
__weak __typeof (self)weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"%@", strongSelf);
}
}];
}
- (void)dealloc {
if (_observer) {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
}
在这段代码中,通过在block
之前定义对self
的弱引用,来解决了循环引用问题。
a.在block之前定义对self的一个弱引用weakSelf,因为是弱引用,所以self被释放时weakSelf会变为nil;
b.在block中引用该弱引用,考虑到多线程情况,通过强引用strongSelf来引用该弱引用,这时如果self不为nil就会retain self,以防止在block内部使用过程中self被释放。
c.在block块中使用该强引用strongSelf,注意对strongSelf进行nil检测,因为多线程在弱引用weakSelf对强引用strongSelf赋值时,弱引用weakSelf可能已经为nil了
d.强引用strongSelf在block作用域结束之后,自动释放。
三、block不需要使用weakSelf情况
当block
本身不被self
持有,而被别的对象持有,同时不产生循环引用的时候,就不需要weakSelf
.最常见的代码就是UIView
的动画代码。我们在使用UIView
的animateWithDuration:animations
方法做动画的时候,并不需要使用weakSelf
, 因为引用的持有关系是:
a.UIView的某个负责动画的对象持有了block
b.block持有了self
因为self并没有持有block,所以就不存储循环引用,因此就不需要使用weakSelf;
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
当动画结束时,UIView
会结束持有这个block
,如果没有别的对象持有block
的话,block
就会释放掉,从而block
就会释放对于self
的持有,整个内存引用关系被解除。
四.weakSelf 为什么需要strongSelf配合使用
在 block
中先写一个 strong self,其实是为了避免在block
的执行过程中,突然出现self
被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。
我们以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代码举例:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
}
};
如果没有 strongSelf 的那行代码,那么后面的每一行代码执行时,self
都可能被释放掉了,这样很可能造成逻辑异常。
特别是当我们正在执行 strongSelf.networkReachabilityStatusBlock(status);
这个 block 闭包时,如果这个block
执行到一半时 self
释放,那么多半情况下会 Crash
。
五.block需要构造循环引用
需要不使用weak self
的场景是:你需要构造一个循环引用,以便保证引用双方都存在。比如你有一个后台的任务,希望任务执行完后,通知另外一个实例。比如开源的YTKNetwork
网络库的源码中,就有这样的场景。
在 YTKNetwork
库中,我们的每一个网络请求API
会持有回调的block
,回调的 block
会持有self
,而如果 self
也持有网络请求API
的话,我们就构造了一个循环引用。虽然我们构造出了循环引用,但是因为在网络请求结束时,网络请求 API
会主动释放对block
的持有,因此,整个循环链条被解开,循环引用就被打破了,所以不会有内存泄漏问题。代码其实很简单,如下所示:
// YTKBaseRequest.m
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
总结来说,解决循环引用问题主要有两个办法:
第一个办法是「事前避免」,我们在会产生循环引用的地方使用 weak 弱引用,以避免产生循环引用。
第二个办法是「事后补救」,我们明确知道会存在循环引用,但是我们在合理的位置主动断开环中的一个引用,使得对象得以回收。
五. 即时不互相持有,也需要weakSelf和strongSelf情况
// 设置 网络 请求
- (void)setupNetworkRequest {
[MBProgressHUD showLoading:nil toView:self.view];
__weak typeof(self) weakSelf = self;
[MSLogisticsListViewModel loadLogisticsInfoWithOrderNo:self.orderNo completion:^(LERequestResultType type, NSArray *dataArray) {
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[MBProgressHUD hideForView:strongSelf.view animated:YES];
if (type == LERequestResultTypeSuccess || type == LERequestResultTypeNoMore || dataArray.count > 0) {
strongSelf.logisticsDiverArray = dataArray;
[strongSelf.tableView hideBlankPage];
[strongSelf.tableView reloadData];
return ;
}
if (type == LERequestResultTypeNoNetWork) {
[strongSelf.tableView showBlankPageWithImage:networkTipsImage
tips:networkBrokenTips];
return ;
}
if (type == LERequestResultTypeServerError) {
[strongSelf.tableView showBlankPageWithImage:serverBusyTipsImage
tips:serverErrorTips];
return;
}
if (dataArray.count == 0) {
[strongSelf.tableView showBlankPageWithImage:logisticsTipsImage
tips:logisticsNullDataTips];
return ;
}
}
}];
}
比如这个由MSLogisticsListViewModel
发起的网络请求,因为MSLogisticsListViewModel
持有block
,block
持有self
,但是self并不持有MSLogisticsListViewModel
,因此这里并不存在循环引用。那为什么也要用weakSelf
和strongSelf
.
因为当网络请求回来后,这里可能会进行怎大量的数据处理和逻辑处理,如果数据处理和逻辑处理相对复杂,处理过程中可能会存在循环引用,如果不用weakSelf
和strongSelf
,那么只有等到block
里面的内容执行完毕之后,才会调用dealloc
方法,如果只是block
内部的处理过程,也存在循环引用,就会导致内存泄漏。
而如果使用了weakSelf
和strongSelf
,那么当该视图销毁的时候,如果block
还没有回调,就会直接先调用dealloc
方法,再走block
,不过这是weakSelf
为nil
,这就能保证比如控制器viewController
进行pop
之后,立即释放。
没有用weakSelf
和strongSelf
:
- (void)dealloc {
NSLog(@"dealloc method");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟 回调");
[self.tableView reloadData];
NSLog(@"tableView 刷新");
});
}
pop viewController
打印结果:
FJChatMessageViewDemo[37477:780816] 延迟 回调
FJChatMessageViewDemo[37477:780816] tableView 刷新
FJChatMessageViewDemo[37477:780816] dealloc method
使用weakSelf
和strongSelf
:
- (void)dealloc {
NSLog(@"dealloc method");
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟 回调");
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[weakSelf.tableView reloadData];
NSLog(@"tableView 刷新");
}
});
}
pop viewController
打印结果:
FJChatMessageViewDemo[37740:787636] dealloc method
FJChatMessageViewDemo[37740:787636] 延迟 回调
六.最后:
送上一张喜欢的图片:
如果觉得不错,麻烦给个喜欢或star,如果有问题请及时反馈,谢谢!