首先,我有一个协议,只定义了一些只读属性,例如:
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
一起使用,因此我无法指定该值一旦生成就永远不会改变。我需要生成值,因为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'
。
答案 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 实现。