iOS 循环引用的问题总结
原因:
self -> Timer -> target(self), 造成循环引用
导致控制器不会销毁,不会调用dealloc 方法,内存泄漏
- (void)dealloc{ [_timer invalidate]; NSLog(@"********************delloc**************8"); }
解决方式:
1.API block的方式 iOS10以后可用
__weak typeof(self)weakself = self; _timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { weakself.count++; NSLog(@"-------%ld",weakself.count); }];
2.手动破解循环
在适当的时机调用 (达到一定条件或者 例如在- (void)viewDidDisappear:(BOOL)animated {}调用,)
[self.timer invalidate];
self.timer = nil;
3. 借助runtime给对象添加消息处理的能力, 这种方式虽然能破解循环,但是 test 方法里获取到的self 是 _objct ,故感觉不太实用
// 借助runtime给对象添加消息处理的能力 _objct = [[NSObject alloc] init]; class_addMethod([_objct class], @selector(test), class_getMethodImplementation([self class], @selector(test)), "v@:"); _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:@"ddd" repeats:YES];
4.消息转发机制
创建一个中间类 PHJProxy,
@interface PHJProxy : NSObject
@property (nonatomic, weak) id target;
@end
@implementation PHJProxy //方法1: //// 发送给target //- (void)forwardInvocation:(NSInvocation *)invocation { // [invocation invokeWithTarget:self.target]; //} //// 给target注册一个方法签名 //- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel { // return [self.target methodSignatureForSelector:sel]; //} // 方法2: //仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。 -(id)forwardingTargetForSelector:(SEL)aSelector { return self.target; } @end
这2种方式原理都是一样,使用消息的转发机制,把消息转发到 target 中
补充:如果PHJProxy类 继承至NSProxy 时:
@interface LWProxy : NSProxy @property (nonatomic, weak) id target; + (instancetype)proxyWithTarget:(id)target; @end /** NSProxy 类,是专门做消息转发的代理类,如果本类中没有方法实现,则不会去父类中查找,直接进入 -()methodSignatureForSelector{},进行转发 因此此类做消息转发比NSObject 效率更高 */ @implementation LWProxy + (instancetype)proxyWithTarget:(id)target { // 只有alloc方法,没有init方法 LWProxy *proxy = [LWProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end
5. NSTimer 分类的方式,
@implementation NSTimer (LWTimer) + (NSTimer *)lw_timerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(timermethod:) userInfo:[block copy] repeats:YES]; } + (void)timermethod:(NSTimer *)timer { void (^block)() = timer.userInfo; if (block) { block(); } } @end
上述创建方式调用者是NSTImer自己,只是NSTimer捕获了参数block。这样我们在使用timer时,由于target的改变,就不再有循环引用了。
使用方式:
__weak typeof(self)weakself = self;
_timer = [NSTimer lw_timerWithTimeInterval:2 block:^{ weakself.count++; NSLog(@"-------%ld",weakself.count); } repeats:YES];
iOS10中,定时器的API新增了block方法,实现原理与此类似,这里采用分类为NSTimer添加了带有block参数的方法,而系统是在原始类中直接添加方法,最终的行为是一致的。
注意:
1、把timer改成弱引用
@property (nonatomic, weak) NSTimer *timer;
虽然self对timer是弱引用,但是控制的delloc方法的执行依赖于timer的invalidate,timer的invalidate又依赖于控制器的delloc方法,这是一个鸡生蛋还是蛋生鸡的问题,依旧是循环引用
2. 使用__weak 那换个思路能不能让NSTimer弱引用target
__weak typeof(self) weakSelf = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(showMsg) userInfo:nil repeats:YES];
weak关键字适用于block,当block引用了块外的变量时,会根据修饰变量的关键字来决定是强引用还是弱引用,如果变量使用weak关键字修饰,那block会对变量进行弱引用,如果没有__weak关键字,那就是强引用。
但是NSTimer的 scheduledTimerWithTimeInterval:target方法内部不会判断修饰target的关键字,所以这里传self 和 weakSelf是没区别的,其内部会对target进行强引用,还是会产生循环引用。