Swift中不可变结构体使用lazy var的问题及优化方案
Swift结构体常量实例的缓存计算通用解决方案
我正在处理一个用于存储数据并执行缓存操作的结构体,示例代码如下:
struct Number { let value: Int lazy var square = pow(Double(value), 2) lazy var squareRoot = Double(value).squareRoot() lazy var factorial = (1...value).reduce(1, *) }
当结构体实例为变量时,运行完全正常:
var number = Number(value: 9) number.square // 81 number.squareRoot // 3 number.factorial // 362 880
但如果是常量实例,访问lazy属性就会报错:
let number = Number(value: 9) number.square // Cannot use mutating getter on immutable value: 'number' is a 'let' constant
现有常见解决方案的局限
- 将
let改为var使实例可变:仅适用于局部变量,函数参数默认是常量,必须在内部创建副本才能使用,示例:func f(number: Number) { var n = number n.factorial } - 将结构体转为类:不符合我的使用场景,无法采用。
- 用额外属性存储缓存结果:可以创建专用类实例存储计算结果,但方案冗余繁琐,每个结构体都要重复定义缓存相关属性,示例:
struct Number { let value: Int class Cache { var square: Double? var squareRoot: Double? var factorial: Int? } private let cache = Cache() var square: Double { guard let square = cache.square else { let res = pow(Double(value), 2) cache.square = res return res } return square } var squareRoot: Double { guard let squareRoot = cache.squareRoot else { let res = Double(value).squareRoot() cache.squareRoot = res return res } return squareRoot } var factorial: Int { guard let factorial = cache.factorial else { let res = (1...value).reduce(1, *) cache.factorial = res return res } return factorial } } let number = Number(value: 9) number.factorial // 362 880
通用便捷的解决方案
可以通过泛型缓存容器实现通用化,避免每个结构体重复编写缓存逻辑:
1. 定义通用缓存类
创建一个泛型类,用来存储任意类型的缓存值:
final class CacheStorage<Value> { private var storage: [String: Value] = [:] func value(forKey key: String, compute: () -> Value) -> Value { if let cached = storage[key] { return cached } let result = compute() storage[key] = result return result } }
2. 在结构体中复用缓存逻辑
在需要缓存计算的结构体中,仅需声明一个CacheStorage实例,对每个需要缓存的属性,通过value(forKey:compute:)方法实现懒加载缓存,无需重复定义缓存属性:
struct Number { let value: Int private let cache = CacheStorage<Any>() var square: Double { cache.value(forKey: "square") { pow(Double(value), 2) } } var squareRoot: Double { cache.value(forKey: "squareRoot") { Double(value).squareRoot() } } var factorial: Int { cache.value(forKey: "factorial") { (1...value).reduce(1, *) } } }
3. 使用验证
现在无论是常量还是变量实例,都能正常访问缓存属性:
let number = Number(value: 9) number.square // 81 number.squareRoot // 3 number.factorial // 362880 var mutableNumber = Number(value: 5) mutableNumber.square //25 mutableNumber.factorial //120
进阶优化:避免字符串键硬编码
为避免字符串键拼写错误,可通过#function关键字自动获取属性名作为缓存键:
struct Number { let value: Int private let cache = CacheStorage<Any>() var square: Double { cache.value(forKey: #function) { pow(Double(value), 2) } } var squareRoot: Double { cache.value(forKey: #function) { Double(value).squareRoot() } } var factorial: Int { cache.value(forKey: #function) { (1...value).reduce(1, *) } } }
这样每个属性的缓存键就是自身的属性名,既安全又简洁。
内容的提问来源于stack exchange,提问作者iUrii
相关产品推荐
相关产品推荐

