Swift Struct with Lazy,私有属性符合Protocol

时间:2015-01-28 21:34:24

标签: swift struct protocols lazy-evaluation

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

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

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

  1. 需要懒惰地生成属性。
  2. 属性是相关的,需要一起生成。
  3. 我似乎找不到办法来做到这一点。我最接近的是这样的:

    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一起使用,因此我无法指定该值一旦生成就永远不会改变。我需要生成值,因为struct是在主线程上创建的,但是值将由后一个进程完全在后台线程上生成。

    更新

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

    例如:

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

    我无法从AStruct访问Container中的变量,因为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'

3 个答案:

答案 0 :(得分:18)

因为访问惰性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 }
}

答案 1 :(得分:3)

所以我想详细说明我最终遵循的解决方案。事实证明,我认为Swift目前无法想到我想要的东西。一旦你从mutating get道路开始,它最终会层叠到太多区域(所有容器结构都需要变异等)。最后,这种破坏了我想首先使用结构的全部原因。

也许在未来的路上,Apple会添加lazy let var = ...。这将通过保证延迟变量只设置一次来确保不变性......只是不立即。

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

所有这一切,@ AirspeedVelocity有一个正确的解决方案,即使我的需求难以为继,所以我会接受他的解决方案。我刚刚离开这里,所以其他人可以理解我是如何克服这个问题的...使用类。

答案 2 :(得分:0)

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

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 实现。