Swift 循环引用

为什么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没有被释放,才能使用无主引用。

相关推荐
fei_sun40 分钟前
RIB、FIB、MAC 、ARP 表
服务器·网络·macos
茶底世界之下3 小时前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Highcharts.js4 小时前
如何在macOS桌面应用集成Highcharts
macos·highcharts·前端可视化·交互图表
ZZH_AI项目交付4 小时前
实时字幕负责快,本地录音负责不丢:一个 iOS 录音功能为什么需要两条链路
ios·ai编程
Cc.Y4 小时前
Swift深度链接的极简之道
ios·cocoa·swift
zaemyn20205 小时前
macOS 上 AccessClient 无法唤起?排查 Python 架构与 Windows App 识别问题
windows·python·macos·远程桌面·accessclient
AI砖家6 小时前
iPhone Duo 上架新规:旧 App 怎么兼容,还能不能继续更新?
ios·cocoa·iphone·ios上架·ios上架新规
黑科技iOS上架6 小时前
小蟹iOS混淆:Objective-C项目实战
ios·审核·ios混淆·执行程序差异化
茶底世界之下6 小时前
照片边缘太抢眼?用 Awaking 做一次 1:1 裁切
ios
Digitally6 小时前
如何在iPhone上永久删除文件而无需恢复
ios·iphone