定时器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 |
|