Timer scheduledTimer 循环引用

先看代码
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的强引用

image.png

所以我们可以知道,这个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()
 

你可能感兴趣的:(Timer scheduledTimer 循环引用)