摆脱Timer内存泄露

来源:互联网 时间:2017-01-22


大家应该或多或少的让Timer给整过,尤其是一个控制器里面有N多个Timer的时候,时不时的会给你带来内存上的各种问题。下面,来用block来解决这个问题。


首先,大部分情况下,我们是这样使用Timer的:


 weak var myTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerAction(timer:)), userInfo: nil, repeats: true)
}
func timerAction(timer: Timer) {
print("timer action")
}
deinit {
print("viewcontroller deinit")
}

这种情况下,一定要在deinit调用之前手动调用 myTimer.invalidate() 才能将Timer和控制器释放掉,不然就会造成循环引用。(有人可能会问: Timer我不是用了weak了吗?为什么还会造成循环引用?)。


关于这个问题,首先要搞清楚,既然你用了weak,而timer仍然可以使用,说明了什么问题?说明了还有强引用在指向timer,才使得它没有被销毁。这个强引用就来自runloop。说到runloop,可以扯出一大堆的知识点(runloop相关文章),这里大家可以参考Timer的官方解释。 也就是说runloop会强引用timer,所以控制器里面只需要weak来定义就可以了。同时,timer又用target强引用了控制器。


所以,如果你没有调用timer的invalidate(),timer就会一直存在,对控制器的强引用也会一直存在。


那么,怎么来解决这个问题呢?

直接上代码:


// 这里不存在循环引用问题,所以可以不使用weak,当然,你也可以使用。 
var myTimer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
// block中使用self,一定要声明[weak self],不然会造成循环引用
myTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, repeats: true, action: { [weak self] in
print("time action")
print(self?.count)
})
}
deinit {
print("viewcontroller deinit")
}

extension Timer {
private class TempWrapper {
var timerAction: () -> ()
weak var target: AnyObject?
init(timerAction: () -> (), target: AnyObject) {
self.timerAction = timerAction
self.target = target
}
}
public class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, repeats: Bool = false, action: () -> ()) -> Timer {
return scheduledTimer(
timeInterval: timeInterval,
target: self,
selector: #selector(self._timeAction(timer:)),
// 这里不能直接传action参数,因为() -> () 是不能赋值给AnyObject类型的(同Objective-C
//中的id类似,代表任意class类型),所以,这里使用TempWrapper类来包装一下
userInfo: TempWrapper(timerAction:action, target: target),
repeats: repeats
)
}
class func _timeAction(timer: Timer) {
if let tempWrapper = timer.userInfo as? TempWrapper {
if let _ = tempWrapper.target {
tempWrapper.timerAction()
} else {
// 如果控制器已经销毁,就把timer invalidate掉
timer.invalidate()
}
}
}
}

这里,给Timer做了一个扩展,自己来定义一个方法来包装Timer原来的方法,目的就是摆脱原来Timer方法中target对控制器的强引用。(新方法中的target参数只是用来判断控制器是否已经销毁,而且是weak,不会造成强引用)


测试demo




相关阅读:
Top