iOS如何巧妙解决NSTimer的循环引用详解
一 发现问题
我们都知道nstimer采用target-action的方式,通常target又是类本身,我们为了方便又把nstimer声明为属性变量,这样就难免会造成循环引用(需要反复执行计时任务时,如果是单次的任务就不会造成循环引用)。
例如:
_timer = [nstimer scheduledtimerwithtimeinterval:5.0 target:self selector:@selector(starttimer) userinfo:nil repeats:yes];
深入理解,类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。
大家可能觉得解决这个问题很简单,在合适的时机释放nstimer,大多人多会选择viewwilldisappear,viewdiddisappear,dealloc。当然了如果选择在dealloc释放nstimer的且觉得这样没问题的,那是你不够了解dealloc的执行时间,科普下dealloc的执行时机是在self释放之后执行的。这样就排除了dealloc,那就只能选择viewwilldisappear,viewdiddisappear(push和pop都会执行)。但是这两个方法往往不能满足需求。
二 解决问题
有去了解nstimer循环引用的同学,知道有两种常见的方法可以解决:
- 采用block封装,target设置为nstimer本身
- 既然是因为target是self本身造成的,那就把target设置为其他对象
(第一种block就不用说了,大家也比较喜欢这种方式,但是有时候就不想用block呢,想用第二种方法,但是用起来有很多不便之处,target是其他对象,action也要在其他对象,这样在action想要访问self的相关信息就很不方便。于是就有第三种方法诞生了。)
3.用一个含有weak属性的对象a包裹self作为target,再对a进行消息转发,访问a就相当于访问self,这样就完美的解决了循环引用,且保留了target-action方式。
大家比较好奇的是有weak属性的对象a的类怎么实现,下面来看看代码:
#import <foundation/foundation.h> #pragma mark - #pragma mark - 内置weak对象(可用于分类定义weak属性) @interface xwweakobject : nsobject @property (nullable, nonatomic, weak, readonly) id weakobject; - (instancetype _nullable )initweakobject:(id _nullable )obj; + (instancetype _nullable )proxyweakobject:(id _nullable )obj; @end #import "xwweakobject.h" @implementation xwweakobject -(instancetype)initweakobject:(id)obj{ _weakobject = obj; return self; } +(instancetype)proxyweakobject:(id)obj{ return [[xwweakobject alloc] initweakobject:obj]; } - (id)forwardingtargetforselector:(sel)selector { return _weakobject; } - (void)forwardinvocation:(nsinvocation *)invocation { void *null = null; [invocation setreturnvalue:&null]; } - (nsmethodsignature *)methodsignatureforselector:(sel)selector { return [nsobject instancemethodsignatureforselector:@selector(init)]; } - (bool)respondstoselector:(sel)aselector { return [_weakobject respondstoselector:aselector]; } - (bool)isequal:(id)object { return [_weakobject isequal:object]; } - (nsuinteger)hash { return [_weakobject hash]; } - (class)superclass { return [_weakobject superclass]; } - (class)class { return [_weakobject class]; } - (bool)iskindofclass:(class)aclass { return [_weakobject iskindofclass:aclass]; } - (bool)ismemberofclass:(class)aclass { return [_weakobject ismemberofclass:aclass]; } - (bool)conformstoprotocol:(protocol *)aprotocol { return [_weakobject conformstoprotocol:aprotocol]; } - (bool)isproxy { return yes; } - (nsstring *)description { return [_weakobject description]; } - (nsstring *)debugdescription { return [_weakobject debugdescription]; } @end
xwweakobject类有一个weak只读weakobject对象(这个类也可以用于分类声明weak属性:分类是本身是不能声明weak属性的)。
用运行时对该类的对象做了消息转发,对象转发,在访问xwweakobject对象的时候相当于访问其属性weakobject对象。
最后看下怎么用代码实现的:
- (void)viewdidload { [super viewdidload]; xwweakobject *target = [xwweakobject proxyweakobject:self]; self.timer = [nstimer scheduledtimerwithtimeinterval:1 target:target selector:@selector(timercount) userinfo:nil repeats:yes]; } -(void)timercount{ } -(void)dealloc{ [_timer invalidate]; _timer = nil; }
前提timer是self的一个属性,创建一个xwweakobject对象target,target是内部weak属性指向self,相当于target拥有self且是weak,self的retain没有加1,timer拥有xwweakobject对象target,target的retain加1,timer和self的直接关系是timer仅是self的一个属性,这样看来并没有形成循环引用。
三 写在最后
虽然这种方式没有block简便,但不失为一种好的方法,保存了系统的方式。喜欢用target-action方式的或者不太熟悉block的可以学一学哦,且xwweakobject能做的不仅仅这些,xwweakobject可以解决很多类似的循环引用问题,解决分类定义weak属性等等
有人可能有疑问,为什么都同样是target-action方式button就不会出现循环引用的问题,有去研究的同学应该都知道uicontrol的内部做了weak操作,即真正持有的时候是weak的并没有导致retain加1,而nstimer由于runloop的原因并没有做weak操作。
闲言杂语
以上内容仅代表个人想法,如果您有更好的想法,更好的解决办法,可以一起探讨。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。