首先,我有一个协议,它只定义了几个只读属性,例如:
protocol Example {
var var1:String { get }
var varArray:[String] { get }
}
然后,我想创建一个符合该协议的结构。 我遇到的问题是我有两个相互冲突的要求:
- 属性需要延迟生成。
- 这些属性是相关的,需要一起生成。
我似乎找不到一种方法来做到这一点。 我最接近的是这样的:
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
变量是可变的,但永远不会改变。 我不能将let
与lazy
一起使用,因此我无法指定值在生成后永远不会更改。 我需要生成值,因为结构是在主线程上创建的,但这些值将由另一个进程在后台线程上生成。
更新
所以有人向我指出,我可以让 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。