先看代码
class timerTestVC: UIViewController {
private var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .white
// 创建了个定时器
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerRuns), userInfo: nil, repeats: true)
}
@objc private func timerRuns(){
debugPrint("-----run------")
}
deinit {
debugPrint("----deinit-----")
}
}
分析
当我们点击返回按钮时,发现 deinit 并未执行,而且定时还在跑。
那么我们就可以知道,在这个controller
中还有对象未配释放调,造成内存泄漏。
我们来分析timer
:
这里我们定义了timer
对象
private var timer: Timer!
并且我们使用它在当前controller
中创建的个定时器,那么我们可以知道,controller
中持有timer
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerRuns), userInfo: nil, repeats: true)
又因为在Timer
中有个target
中持有个self
也就是当前的controller
target: self
还有timer在创建的时候需要将其加入到RunLoop
中,主线程中的RunLoop
是常驻内存同时对Timer的强引用
所以我们可以知道,这个controller
中出现了循环引用的问题。
解决方案
一、 用block
方式创建timer
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { time in
debugPrint("-----run------")
})
二、使用消息转发机制 self
用一个中间类代替
1、使用 NSObject
方式
创建一个类TimerProxy
集成自NSObject
private weak var target: AnyObject!
init(target: AnyObject){
self.target = target
super.init()
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return self.target
}
那么在调用的时候这样:target: TimerProxy(target: self)
timer = Timer.scheduledTimer(timeInterval: 1, target: TimerProxy(target: self), selector: #selector(timerRuns), userInfo: nil, repeats: true)
2、使用NSProxy
的方式
但其在 Swift
中会有两个报错的问题
1、init无法重载,这个倒不是很要紧。
2、NSInvocation不可用,这就很关键了,直接导致了forwardInvocation方法无法重载
NSInvocation' is unavailable in Swift: NSInvocation and related APIs not available
因此我们放弃直接使用 NSProxy
的方法。但我们能否根据NSProxy
的原理来自己实现一个Proxy
呢?
NSProxy
是一个消息重定向的一个抽象类,类似一个中间代理人,通过消息转发的机制将消息转发到另一个实例
在OC
中重写以下两个方法即可
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
那么我们是否可以通过方法替换的方式来达到目的呢?
将实例中的Selector
传入进来
required init(target: NSObjectProtocol, sel: Selector)
用这个来获取一个自定义的方法
class_getInstanceMethod(self.classForCoder, #selector(targetMethod))
用此方法将sel
替换成targetMethod
class_replaceMethod(self.classForCoder, sel, method_getImplementation(tagMethod!), method_getTypeEncoding(tagMethod!))
class TimerProxy2: NSObject {
public weak var target: NSObjectProtocol?
public var sel:Selector?
required init(target: NSObjectProtocol, sel: Selector) {
super.init()
self.target = target
self.sel = sel
let tagMethod = class_getInstanceMethod(self.classForCoder, #selector(targetMethod))
class_replaceMethod(self.classForCoder, sel, method_getImplementation(tagMethod!), method_getTypeEncoding(tagMethod!))
}
@objc private func targetMethod(){
if self.target != nil {
self.target?.perform(self.sel)
}
}
}
三、使用GCD的方式创建个定时器
var total = 60
let timer = DispatchSource.makeTimerSource()
timer.schedule(wallDeadline: DispatchWallTime.now(), repeating: DispatchTimeInterval.seconds(1), leeway: DispatchTimeInterval.milliseconds(0))
timer.setEventHandler {
if total <= 0{
timer.cancel()
DispatchQueue.main.async {
}
}else{
DispatchQueue.main.async {
total -= 1
}
}
}
timer.resume()