Swift Struct with Lazy, 私有财产符合协议



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

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:() -> ()
}

如果我在处理类中设置一个接收要处理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。 我使类在功能上不可变,所以我仍然保留它。从字面上看,我所要做的就是将struct更改为class,现在懒惰的构造工作得很好,我所有的问题都消失了......除了我正在使用一个类。

综上所述,@AirspeedVelocity有一个正确的解决方案,即使我的需求站不住脚,所以我会接受他的解决方案。 我只是把这个留在这里,以便其他人可以理解我是如何克服这个问题的......使用类。

您还可以通过将类中延迟分配的存储装箱来解决此问题。你只需要小心,如果有人复制你的结构,副本不会共享延迟分配的状态,除非这有意义。下面是一个示例:

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()来实现写入时复制,但只能来自突变的函数/getter。

最新更新