Swift 惰性结构,私有 属性 符合协议

Swift Struct with Lazy, private property conforming to Protocol

首先,我有一个只定义几个只读属性的协议,例如:

protocol Example {
  var var1:String { get }
  var varArray:[String] { get }
}

然后我想创建一个符合该协议的结构。我 运行 遇到的问题是我有两个相互矛盾的要求:

  1. 属性需要延迟生成。
  2. 属性是相关的,需要一起生成。

我似乎找不到执行此操作的方法。我最接近的是这样的:

struct AStruct : Example {
  private lazy var data:(var1:String, varArray:[String]) = {
    var stringValue:String = ""
    var stringArray:[String] = []
    //Generate data
    return (stringValue, stringArray)
  }()

  var var1:String {
    return self.data.var1
  }

  var varArray:[String] {
    return self.data.varArray
  }
}

问题是,我收到错误:Immutable value of type 'AStruct' only has mutating members named 'data'

有谁知道我可以实现目标的方法吗?从技术上讲,data 变量是可变的,但永远不会改变。我不能将 letlazy 一起使用,因此我无法指定该值一旦生成就永远不会改变。不过,我需要生成值,因为该结构是在主线程上创建的,但是这些值将由另一个进程在后台线程上完全生成。

更新

所以有人向我指出我可以在协议和结构中创建 getter mutating。这行得通,除了我现在有一个问题,我不能在任何其他结构(我是)中使用这个结构。所以最后,我将问题推到了另一个我不想可变的结构中。

例如:

struct Container {
  let astruct:AStruct
  let callback:() -> ()
}

我无法从 Container 访问 AStruct 中的变量,因为 Container 是不可变的并且 AStruct 的成员变量正在发生变化。尝试访问它们会给我之前提到的相同错误消息。

将容器更改为使用 var 而不是 let 会产生相同的错误:

struct Container {
  var astruct:AStruct
  let callback:() -> ()
}

如果我在处理 class 中设置一个接收 Container 来处理的函数:

func processContainer(cont:Container){
  self.doSomething(cont.astruct.var1)
}

我得到同样的错误:Immutable value of type 'AStruct' only has mutating members names 'sql'

因为访问惰性 data 变量会改变 AStruct,所以对它的任何访问都必须标记为也在改变结构。所以你需要写:

struct AStruct : Example {
    // ...
    var var1: String {
        // must declare get explicitly, and make it mutating:
        mutating get {
            // act of looking at data mutates AStruct (by possibly initializing data)
            return self.data.var1
        }
    }

    var varArray:[String] {
        mutating get {
            return self.data.varArray
        }
    }
}

但是,您现在会发现 Swift 抱怨您不符合 Example,因为它的 get var1 没有标记为变异。所以你必须改变它以匹配:

protocol Example {
    var var1:String { mutating get }
    var varArray:[String] { mutating get }
}

所以我想详细说明我最终遵循的解决方案。事实证明,我认为我想要的目前在 Swift 中是不可能的。一旦你开始走 mutating get 路,它最终会级联到太多区域(所有容器结构都需要变异,等等)。最后,这种破坏了我最初想使用结构的全部原因。

也许苹果以后会添加一个 lazy let var = ...。这将通过保证惰性变量只设置一次来确保不变性......只是不是立即设置。

所以我的解决方案就是完全放弃 structs 并改用 classes。我保持 classes 在功能上不可变,所以我仍然保留它。从字面上看,我所要做的就是将 struct 更改为 class,现在惰性构造完美运行,我所有的问题都消失了......除了我使用的是 class.

综上所述,@AirspeedVelocity 有一个正确的解决方案,即使无法满足​​我的需求,所以我会接受他的解决方案。我只是把它留在这里,这样其他人就可以理解我是如何克服这个问题的……使用 classes.

您也可以通过将延迟分配的存储装箱在 class 中来解决这个问题。您只需要小心,如果有人复制了您的结构,则该副本不会共享延迟分配的状态,除非这是有道理的。这是一个例子:

class LazyValue<T> {
    var _storage : T?
    var body : () -> T
    var value : T {
        if let value = _storage {
            return value
        }
        let value = body()
        _storage = value
        return value
    }
    
    init(_ body : @escaping () -> T) { self.body = body }
}

struct StructWithLazyValue {
    let _lazyString = LazyValue<String> {
        "the date is = \(Date())"
    }
    
    var lazyString : String { _lazyString.value }
}

从技术上讲,这根本不会改变结构,因此您不必使用改变 get。另一方面,您的结构现在指向某个共享状态,您在复制它时可能不得不担心。如果这是一个问题,您可以使用 isKnownUniquelyReferenced() 来实现写时复制,但只能来自变异 func/getter.