循环引用的本质: 相互持有
循环引用的场景:代理
、Block
、Timer,CADisplayLink
、Notification
、GCD
等
. Delegate导致的循环引用
在开发中代理使用的比较多,如VC和代理相互持有。
解决:将Delegate 使用weak进行修饰即可解决
. Block 导致循环引用
并不是说Block当中使用了self就一定会造成循环引用,例如使用系统的方法
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0, completion = NULL
————————————————
版权声明:本文为CSDN博主「月月_月」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_24656927/article/details/82144144
可以这样理解,当前的self是无法持有一个类对象的。
有些系统方法Block当中使用self,也是会造成循环引用的
见下面“Notification 使用BLock循环引用”有详细说明
其实只要抓住循环引用的本质,就不难理解。所谓循环引用,是因为当前控制器在引用着block,而block又引用着self即当前控制器,这样就造成了循环引用。系统的block或者AFN等block的调用并不在当前控制器中调用,那么这个self就不代表当前控制器,那自然也就没有循环引用的问题。以上引用均指强引用。
典型的Block循环引入如下
代码:
typedef void(^testBlock)(void);
@interface ViewController ()
@property(nonatomic,copy) testBlock block;
@property(nonatomic,strong) NSString *name;
@end
self.block = ^{
self.name = @"";
};
会导致内测泄露,可通过Debug Memory Graph查看
[图片上传失败...(image-c189ef-1645759947014)]
解决:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"";
};
. 外部的weakSelf是为了打破强引用环,从而使得没有循环引用。
. 内部的strongSelf是为了在block执行完成前,self不会被释放。因为strongSelf是局部变量,在block执行结束后会释放,所以不会造成循环引用。
. NSTimer引起的内存泄露
NSTimer 被VC 强引用,使用时,需要在VC将要销毁时对timer进行处理
@interface ViewController ()
@property(nonatomic,copy) testBlock block;
@property(nonatomic,strong) NSString *name;
@property (nonatomic, strong) NSTimer *timer;
@end
使用Block回调处理的Timer
- (void)timerTest {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
self.name = @"xx";
NSLog(@"----timer run ");
}];
}
结果是VC销毁了但是定时器还在运行
解决:在dealloc中执行Timer的invalidate。
// 如果不处理,VC销毁了但是定时器还在运行
- (void)dealloc {
[self.timer invalidate];
}
但是还是有问题self不能释放,Timer也在继续执行,正是由于RunLoop强引用计时器,计时器强引用block,block强引用self,所以导致VC被pop后不能释放。
解决:
使用block的方式时,可以通过在block中使用self弱引用的强引用的方式来解决:
- (void)timerTest {
__weak __typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"";
NSLog(@"----timer run %p",strongSelf);
}];
}
如果定时器不是使用Block进行处理的而是使用selector 进行处理的,上述方法就不行了。
原因:
https://www.jianshu.com/p/e8fc6c2b3afa
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
target:weakSelf
selector:@selector(fireHome)
userInfo:nil
repeats:YES];
解决该问题可以使用NSProxy 进行处理
NSProxy:
NSProxy是和NSObject同级的一个类,可以说它是一个虚拟类,它只是实现了<NSObject>的协议;
OC是单继承的语言,但是基于运行时的机制,却有一种方法让它来实现一下"伪多继承",就是利用NSProxy这个类;
OC中的NSProxy类,填补了"多继承"这个空白;
通过继承NSProxy,并重写这两个方法以实现消息转发到另一个实例。(会直接进入消息转发不会通过方法查找)
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
使用场景
场景:
使用 NSTimer or CADisplayLink 定时器时,target是自己的情况下被强引用,即使使用weakself也无效(同上)
原理:
使用Proxy类型占用target(self),使用方法实现消息转发,也就是说定时器不在占有target,而使用proxy类。(第三方框架YYKit中的YYWeakProxy类就是解决了定时器强引用问题)
示例代码:
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)initTimer {
YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
_timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
}
- (void)tick:(NSTimer *)timer {
...
}
在YYWeakProxy中主要重写了下面两个方法
//重写NSProxy如下两个方法,在处理消息转发时,将消息转发给真正的Target处理
自己简单实现如下:
@interface WeakProxy : NSProxy
@property(nonatomic, weak) id target;
+(WeakProxy*)proxyWithTarget:(id)target;
@end
+(WeakProxy*)proxyWithTarget:(id)target {
WeakProxy *proxy = [WeakProxy alloc];
proxy.target = target;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
注意
NSProxy类是没有init方法的,也就是说如果我们要获得一个NSProxy的实例,代码只需要这样:
WeakProxy *proxy = [WeakProxy alloc];
. Notification 使用BLock循环引用
如果是下面这样不会造成循环引用,但是如果usingBlock 里面使用了self的相关属性,就会造成循环引用,
@property (nonatomic, strong) id observer; //持有注册通知后返回的对象
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"notifitest" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
// NSLog(@"--- addObserverForName %@",self.name);//会造成循环引用
NSLog(@"--- addObserverForName");//不会造成循环引用
}];
原因,先看此方法描述
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
// The return value is retained by the system, and should be held onto by the caller in
// order to remove the observer with removeObserver: later, to stop observation.
文档中明确说明了,调用者会持有返回的observer对象。 VC -> observer -> block, block -> VC,
另外注意系统建议我们保持这个对象用于移除通知。
解决:
使用weak
__weak __typeof(self) weakSelf = self;
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"notifitest" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"--- addObserverForName %@",weakSelf.name);//不会造成循环引用
}];
. GCD 导致的循环引用
GCD使用参数包含self,且Block中也包含self,那么此时就要考虑循环引用的问题了:
@property (nonatomic,retain) dispatch_queue_t operationsQueue;
//tips:在iOS6.0之前也就是MRC时代是使用assign。
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
其他:关于多线程通知
NSNotificationCenter消息的接受线程是基于发送消息的线程的。也就是同步的,因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。
//接受消息通知的回调
- (void)testNotifi {
NSLog(@"----testNotifi");
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"---main");
} else {
NSLog(@"----not main");
}
dispatch_async(dispatch_get_main_queue(), ^{
//do your UI
});
}
//发送消息的线程
- (void)sendNotification {
// 子线程发的通知只能在子线程接受到
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(defaultQueue, ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
});
// [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
}
以下不会导致循环引用(没有相互引用):
参考:
https://www.zhihu.com/question/36358590/answer/67885408
https://zhuanlan.zhihu.com/p/141817188
https://zhuanlan.zhihu.com/p/361247301
https://www.jianshu.com/p/7daf2b32fc8f
https://www.jianshu.com/p/1d68b3aa4158