Swift 惰性结构,私有 属性 符合协议
Swift Struct with Lazy, private property conforming to Protocol
首先,我有一个只定义几个只读属性的协议,例如:
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:() -> ()
}
如果我在处理 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.
首先,我有一个只定义几个只读属性的协议,例如:
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:() -> ()
}
如果我在处理 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.