定时器Timer在开发过程中十分常见, 并不是所有使用Timer的地方都会产生循环引用,但是一旦产生就很难释放,我们平常使用Timer的姿态存在一些理所当然的错误,今天我们一起来纠正他.
Timer的使用方式
按照启动方式分为两种:
方式一:自启动
1 |
|
方式二:需要受到添加到runloop才会启动
1 | |
按照事件回调方式又分为两种:
Block回调:
1 |
|
Target-action:
1 | |
平常使用Timer的方式
强引用一个timer
1 | |
在viewDidLoad中初始化
1 | |
使用完之后再deinit中释放
1 |
|
看上去很完美, 实际使用过程中也没有任何问题, Timer也能被正常的释放.
但是问题来了, 我们使用的是 scheduledTimer 的方式初始化的Timer, 如果我们换成如下方式是否可行呢:
1 | |
咦!! 居然也可以,一切正常. 此时我们一般会草率的判断Timer的释放时机就是这样的. 知道有一天我们把初始化的代码写成下面的样子:
1 | |
再或者这样子:
1 | |
你会发现,上面两种写法Timer是无法进行释放的. 细心的同学可能已经发现, 使用 block方式 传递事件的方式都能正确释放, 使用Target-action的方式响应事件的不能正确释放. 是的, 的确是这样, 因为使用Block时系统对 self做了弱引用处理, 所以不会产生循环引用, 但是Target-action方式却没有,故而产生循环引用, 既然产生了循环引用,那么 deinit方式也不会被调用, 所以Timer不会释放咯.
如何解决循环引用的问题
上面我们找到了循环引用的原因, 那么解决办法就会有很多, 有的同学第一反应就是: 那就都是用Block方式, 不使用Target-action.
这是个好方法, 但是限制了我的使用方式, 不舒服
又有同学会说, 那就叫在viewWillDisappear中释放timer, 问题如下:
页面即将消失的时候销毁timer单独看起来没有问题, 但是如果我们以后两个页面使用push方式切换时 A -> push -> B, 如果timer在B中, 那么我们使用手势滑动pop到A时,会触发viewWillDisappear,此时Timer会被销毁,但是如果我们中途取消滑动,又回到B,那么Timer就位nil, 在使用Timer程序就会崩溃.除非我们相应的在viewWillAppear中再次创建Timer,但是不推荐此做法.
那么我们在viewDidDisappear中销毁Timer咋样呢, 也有问题:
还是A -> push -> B, 此时我们Timer在A中, push到B后A的viewDidDisappear会被调用, 那么定时器被销毁, 当我们回到A时Timer为nil,调用Timer程序也会崩溃,除非我们相应的在viewWillAppear中再次创建Timer,但是我也不推荐此做法.
最好的释放Timer的方式
通过Target-action 方式Timer不能释放,是因为 Timer强引用的target, 也就是self. 所以我们可以新建一个类,用来初始化timer, 以及响应timer的时间, 然后通过block将事件的响应结果回调出去.这样还能将业务和UI分离,代码更加简洁易懂. 具体的实现如下:
1 | |
调用方式也十分简单:
1 | |
释放方式就能直接在deint中释放:
1 |
|