You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:35:22