“Are you telling me a readonly property is wrecking my performance?”(你是说 readonly 属性正在拖垮我的性能?)——当这个提问在技术论坛上一出现,立刻引发了众多 iOS 和 macOS 开发者的热烈讨论。只读属性看似无害,但在某些场景下,它可能成为性能瓶颈的“隐形杀手”。

只读属性的“无辜外表”

在 Swift 或 Objective-C 开发中,readonly 属性被广泛认为是安全、高效的编码方式。它允许其他对象访问数据,同时防止外部修改。多数开发者默认认为,相比读写属性,只读属性的性能开销更小。然而,真实情况并非如此简单。

性能问题的根源在于:readonly 属性并非你想象的那样“只读”。在编译层面,使用 @property(readonly) 声明的属性,系统仍然会生成 getter 方法。而当该属性是计算属性(computed property)或带有属性观察器(property observer)时,性能损失会被成倍放大。

隐藏在 getter 中的性能陷阱

假设你写了这样一个只读属性:

var cachedUserData: [String: Any]? {
    return loadExpensiveDataFromDisk()
}

每次访问这个属性,系统都会执行耗时操作。开发者可能误以为属性是缓存好的,事实却恰恰相反。

从底层原理看,readonly 属性的 getter 方法会在每次访问时被调用。如果 getter 内部涉及文件读取、网络请求、大量计算或数据库查询,就会导致严重的性能下降,甚至引发界面卡顿。

案例实证:一次典型的性能危机

某金融类 App 在页面切换时出现了明显卡顿。团队排查了所有网络请求和视图层级,却毫无头绪。最终通过 Instruments 分析,发现罪魁祸首竟是一个 readonly 属性。

该属性每隔 50 毫秒被界面刷新方法访问,每次访问都触发 Core Data 查询并创建多个临时对象。仅此一处,就占据了主线程 60% 的 CPU 时间片。

解决方案:从源头破局

要避免 readonly 属性成为性能黑洞,开发者需要建立正确的思维模型:

  1. 区分存储属性与计算属性:使用 letlazy var 声明真正的只读存储属性,而非通过计算属性模拟。

  2. 显式缓存计算结果:如果需要频繁访问一个耗时计算的结果,使用 lazy var 或手动实现缓存逻辑。

  3. 使用 Instruments 进行性能分析:不要放过任何频繁调用的 getter 方法,尤其是在高频循环或更新周期中。

  4. 考虑使用函数替代:如果属性访问涉及明显副作用或耗时操作,改用暴露方法(method)更为合适。

更优选择:现代 Swift 的新思路

在 Swift 5.5 及更高版本中,开发者可以利用 @MainActor 和 async/await 设计异步只读接口,从根本上避免在主线程进行耗时操作。同时,@propertyWrapper 也可以帮助封装缓存逻辑,使代码更简洁、性能更可控。

总结:警惕“语法糖陷阱”

return aReadonlyProperty看似轻量的操作,其幕后可能隐藏着复杂逻辑。readonly 属性是一把双刃剑——它让接口清晰,却可能让性能受损。开发者应当摒弃“只读 = 零开销”的思维定式,重视对 getter 方法的性能分析,在未来版本迭代中做到优雅与高效兼得。