为什么Swift 会发生循环引用
A的实例持有B的实例,B的实例持有A的实例。
如何解决循环引用
使用弱引用,无主引用 本质是断开循环引用
弱引用
代理设计模式
swift
/// NonisolatedNonsending
protocol DownloadManagerDelegate: AnyObject {
func downloadManager(_ manager: DownloadManager, didProgressTo percentage: Double)
}
class DownloadManager {
weak var delegate: DownloadManagerDelegate?
func startDownload() async {
for i in 0...100 {
try? await Task.sleep(nanoseconds: 500_000_000)
self.delegate?.downloadManager(self, didProgressTo: Double(i) / 100)
}
}
}
class ViewController: NSViewController, DownloadManagerDelegate {
var manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
Task {
await manager.startDownload()
}
}
func downloadManager(_ manager: DownloadManager, didProgressTo percentage: Double) {
print("download: \(percentage)")
}
}
ViewController 的生命周期 > DownloadManager 的生命周期,这里使用weak var optional. ViewController 的实例持有DownloadManager 的实例,DownloadManager 的实例并没有持有ViewController,打破了循环引用。当ViewController销毁时,DownloadManager 的实例也会销毁。
闭包
swift
class DownloadManager {
var onProgress: ((Double) -> Void)?
func startDownload() async {
for i in 0...100 {
try? await Task.sleep(nanoseconds: 500_000_000)
let percentage = Double(i) / 100
if let onProgress {
onProgress(percentage)
}
}
}
}
class ViewController: NSViewController {
var manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.onProgress = { [weak self] percentage in
self?.updateUI(progress: percentage)
}
Task {
await manager.startDownload()
}
}
func updateUI(progress: Double) {}
}
解释:这里的ViewController 的实例持有manager, manager 的onProgress 使用捕获列表 使用weak关键字,弱引用了ViewController 的实例,从而打破了循环引用。
无主引用
swift
class Greeter {
var name: String?
lazy var greeting = { [unowned self] in
return "Hello, \(self.name ?? "Swift")"
}
deinit {
print("Greeter deinit")
}
}
// Client
let greeter = Greeter()
let greeting = greeter.greeting()
print(greeting)
// **Hello, Swift**
// **Greeter deinit**
和弱引用类似,无主引用也不会引用计数+1。Greeter 实例持有了greeting, greeting 无主引用了Greeter 实例, 当greeter 除了作用域后,Greeter 实例释放。 和弱引用不同的是,无主引用无需解包,使用更简洁。然而,这也会给我们带来风险。使用无主引用时,一定要确保self一定存在。在上例中,lazy var greeting的闭包是在第一次访问 greeting属性时才被创建并存储的。此时 self(Greeter实例)必然存在,因为你必须通过某个实例才能访问。
swift
class Greeter {
var name: String?
lazy var greeting = {
return "Hello, \(self.name ?? "Swift")"
}()
deinit {
print("Greeter deinit")
}
}
let greeter = Greeter()
let greeting: String = greeter.greeting
print(greeting)
// **Hello, Swift**
// **Greeter deinit**
此时greet不再是闭包类型(String)。Greeter并没有持有返回字符串的闭包。闭包执行完就释放了,所以此时不存在循环引用。
再看一个变体。
swift
class Greeter {
var name: String?
lazy var greeting = { [name] in
return "Hello, \(name ?? "Swift")"
}
deinit {
print("Greeter deinit")
}
}
let greeter = Greeter()
let greeting = greeter.greeting()
print(greeting)
// **Hello, Swift**
// **Greeter deinit**
此时我们捕获了name,因为 name是值类型,闭包捕获的是它的一个副本, greeting 闭包不再持有 self,所以不存在循环引用。
总结
解决循环引用时,大部分的情况都可以使用弱引用;只有在闭包执行时,确定self没有被释放,才能使用无主引用。