一、循环引用原理
NSTimer 会强引用它的 target
。如果 target
(如 ViewController
)又直接或间接强引用了 timer
,会导致循环引用,内存无法释放。
swift
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Timer 强引用 self,self 强引用 Timer → 循环引用
timer = Timer.scheduledTimer(
timeInterval: 1,
target: self,
selector: #selector(handleTimer),
userInfo: nil,
repeats: true
)
}
@objc func handleTimer() {
print("Timer fired")
}
deinit {
timer?.invalidate()
print("Deinit") // 不会执行
}
}
二、解决方案
1. 使用中间代理对象(Weak Proxy)
创建一个弱引用代理对象,打破 Timer
和 ViewController
之间的强引用。
swift
class WeakProxy: NSObject {
weak var target: NSObjectProtocol?
init(target: NSObjectProtocol) {
self.target = target
super.init()
}
// 将方法转发给真正的 target
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
}
}
// 在 ViewController 中使用
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Timer 强引用 WeakProxy,WeakProxy 弱引用 self
timer = Timer.scheduledTimer(
timeInterval: 1,
target: WeakProxy(target: self),
selector: #selector(handleTimer),
userInfo: nil,
repeats: true
)
}
@objc func handleTimer() {
print("Timer fired")
}
deinit {
timer?.invalidate()
print("Deinit") // 正常执行
}
}
2. 使用 Block-Based API(iOS 10+)
利用 scheduledTimer(withTimeInterval:repeats:block:)
,通过 weak self
避免循环。
swift
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Block 内捕获 weak self,避免循环
timer = Timer.scheduledTimer(
withTimeInterval: 1,
repeats: true
) { [weak self] _ in
self?.handleTimer()
}
}
func handleTimer() {
print("Timer fired")
}
deinit {
timer?.invalidate()
print("Deinit") // 正常执行
}
}
3. 手动销毁 Timer
在 viewWillDisappear
或 deinit
中手动销毁 Timer
。
swift
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(
timeInterval: 1,
target: self,
selector: #selector(handleTimer),
userInfo: nil,
repeats: true
)
}
@objc func handleTimer() {
print("Timer fired")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timer?.invalidate()
}
deinit {
print("Deinit") // 正常执行
}
}
三、总结
方案 | 核心思想 | 优点 | 缺点 |
---|---|---|---|
Weak Proxy | 中间对象弱引用 target |
兼容旧版本 | 需额外代理类 |
Block-Based API | 利用 weak self 捕获弱引用 |
代码简洁(iOS 10+) | 仅支持 iOS 10+ |
手动销毁 Timer | 在适当时机调用 invalidate() |
简单直接 | 需确保销毁逻辑被执行 |
选择方案时,优先推荐 Block-Based API (iOS 10+)或 Weak Proxy(兼容旧版本)。