我有一个初始化为的变量:
lazy var aClient:Clinet = {
var _aClient = Clinet(ClinetSession.shared())
_aClient.delegate = self
return _aClient
}()
问题是,在某些时候,我需要重置此aClient
变量,以便在ClinetSession.shared()
更改时再次初始化。但是如果我将类设置为可选Clinet?
,当我尝试将其设置为nil
时,LLVM会给我一个错误。如果我只是使用aClient = Clinet(ClinetSession.shared())
将其重置在代码中的某个位置,则最终会使用EXEC_BAD_ACCESS
。
有没有办法可以使用lazy
并允许自行重置?
答案 0 :(得分:78)
lazy明确表示只进行一次初始化。您要采用的模型可能只是初始化按需模型:
var aClient:Client {
if(_aClient == nil) {
_aClient = Client(ClientSession.shared())
}
return _aClient!
}
var _aClient:Client?
现在只要_aClient
为nil
,它就会被初始化并返回。可以通过设置_aClient = nil
答案 1 :(得分:39)
由于lazy
的行为在Swift 4中发生了变化,我写了一些struct
来提供非常具体的行为,这些行为在语言版本之间永远不会改变。它们位于CC0
许可下的GitHub上:https://github.com/BenLeggiero/Swift-Lazy-Patterns
ResettableLazy
以下是与此问题相关的问题,它为您提供了一种懒惰初始化值,缓存该值并将其销毁以便以后可以延迟重新初始化的方法:
/// Simply initializes a value
public typealias Initializer<Value> = () -> Value
/// Defines how a lazy pattern should look
public protocol LazyPattern {
/// The type of the value that will be lazily-initialized
associatedtype Value
/// Gets the value, possibly initializing it first
var value: Value { mutating get }
}
/// A resettable lazy pattern, whose value is generated and cached only when first needed, and can be destroyed when
/// no longer needed.
///
/// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use `Lazy`
/// instead wherever possible.
public struct ResettableLazy<_Value>: LazyPattern {
public typealias Value = _Value
/// Holds the internal value of this `Lazy`
private var valueHolder: ResettableValueHolder<Value>
/// Creates a resettable lazy pattern with the given value initializer. That closure will be called every time a
/// value is needed:
///
/// 1. The first time `value` is called, the result from `initializer` will be cached and returned
/// 2. Subsequent calls to get `value` will return the cached value
/// 3. If `clear()` is called, the state is set back to step 1
///
/// - Parameter initializer: The closure that will be called every time a value is needed
public init(initializer: @escaping Initializer<Value>) {
valueHolder = .unset(initializer)
}
/// Sets or returns the value held within this struct.
///
/// If there is none, it is created using the initializer given when this struct was initialized. This process only
/// happens on the first call to `value`;
/// subsequent calls are guaranteed to return the cached value from the first call.
///
/// You may also use this to set the value manually if you wish.
/// That value will stay cached until `clear()` is called.
public var value: Value {
mutating get {
switch valueHolder {
case .hasValue(let value, _):
return value
case .unset(let initializer):
let value = initializer()
valueHolder = .hasValue(value, initializer)
return value
}
}
set {
valueHolder = .hasValue(newValue, valueHolder.initializer)
}
}
/// Resets this lazy structure back to its unset state. Next time a value is needed, it will be regenerated using
/// the initializer given by the constructor
public mutating func clear() {
valueHolder = .unset(valueHolder.initializer)
}
}
// NOTE: This would be nested within `ResettableLazy`, but that caused a runtime crash documented in
// https://bugs.swift.org/browse/SR-7604
/// Takes care of keeping track of the state, value, and initializer as needed
private enum ResettableValueHolder<Value> {
/// Indicates that a value has been cached, and contains that cached value, and the initializer in case the
/// value is cleared again later on
case hasValue(Value, Initializer<Value>)
/// Indicates that the value has not yet been created, and contains its initializer
case unset(Initializer<Value>)
/// Finds and returns the initializer held within this enum case
var initializer: Initializer<Value> {
switch self {
case .hasValue(_, let initializer),
.unset(let initializer):
return initializer
}
}
}
这可以这样使用:
var myLazyString = ResettableLazy<String>() {
print("Initializer side-effect")
return "Hello, lazy!"
}
print(myLazyString.value) // Initializes, caches, and returns the value
print(myLazyString.value) // Just returns the value
myLazyString.clear()
print(myLazyString.value) // Initializes, caches, and returns the value
print(myLazyString.value) // Just returns the value
myLazyString.value = "Overwritten"
print(myLazyString.value) // Just returns the value
myLazyString.clear()
print(myLazyString.value) // Initializes, caches, and returns the value
这将打印:
Initializer side-effect
Hello, lazy!
Hello, lazy!
Initializer side-effect
Hello, lazy!
Hello, lazy!
Overwritten
Initializer side-effect
Hello, lazy!
这种模式的一个主要优点是它可以存储nil
就好了,而其他模式实际上没有为此定义行为:
var myLazyString = ResettableLazy<String?>() {
print("Initializer side-effect")
return nil
}
print(myLazyString.value) // Initializes, caches, and returns the value
print(myLazyString.value) // Just returns the value
myLazyString.value = "Overwritten"
print(myLazyString.value) // Just returns the value
myLazyString.clear()
print(myLazyString.value) // Initializes, caches, and returns the value
这将打印:
Initializer side-effect
nil
nil
Optional("Overwritten")
Initializer side-effect
nil
相反,我建议您使用上面列出的解决方案之一,或@PBosman's solution
以下行为是一个错误,在Swift bug SR-5172中描述(已于2017-07-14与PR #10,911一起解决),很明显这种行为从来都不是故意的。
Swift 3的解决方案由于历史原因而在下面,但因为它是一个在Swift 3.2+中无效的漏洞利用我建议你做不这样做:
我不确定这个添加的确切时间,但是从 Swift 3 开始,您可以简单地使该属性无法使用:
lazy var aClient:Client! = { var _aClient = Client(ClinetSession.shared()) _aClient.delegate = self return _aClient }()
现在,下次在将其设置为
// ...
aClient = nilnil
后调用aClient时,它将重新初始化。 --- 请注意,虽然它现在在技术上是可选的,但每次尝试读取它时,都保证具有运行时值。这就是我在这里使用!
的原因,因为它始终是一个安全的调用,永远不会读取nil
,但它可以 set 到nil
。
答案 2 :(得分:6)
nil
。
编辑2:似乎nil
能够使用懒惰的变量。
非常参加晚会,甚至不确定这是否与Swift 3相关,但是这里有。大卫的答案很好,但如果你想创造许多懒惰的无法变量,你将不得不写一个相当庞大的代码块。我试图创建一个封装此行为的ADT。这是我到目前为止所得到的:
struct ClearableLazy<T> {
private var t: T!
private var constructor: () -> T
init(_ constructor: () -> T) {
self.constructor = constructor
}
mutating func get() -> T {
if t == nil {
t = constructor()
}
return t
}
mutating func clear() { t = nil }
}
然后你会声明并使用这样的属性:
var aClient = ClearableLazy(Client.init)
aClient.get().delegate = self
aClient.clear()
有些事我还不喜欢,但不知道如何改进:
get()
非常糟糕。如果这是一个计算属性而不是一个函数,那会好一点,但计算属性不能改变。get()
的需要,您必须使用此ClearableLazy
的初始化程序扩展您要使用此类型的所有类型。如果有人想从这里捡起它,那就太棒了。
答案 3 :(得分:3)
这允许将属性设置为nil
以强制重新初始化:
private var _recordedFileURL: NSURL!
/// Location of the recorded file
private var recordedFileURL: NSURL! {
if _recordedFileURL == nil {
let file = "recording\(arc4random()).caf"
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(file)
NSLog("FDSoundActivatedRecorder opened recording file: %@", url)
_recordedFileURL = url
}
return _recordedFileURL
}
答案 4 :(得分:1)
这里有一些很好的答案。在许多情况下,重置懒惰变量确实是可取的。
我认为,您还可以定义一个闭包来创建客户端并使用此闭包重置lazy var。像这样:
class ClientSession {
class func shared() -> ClientSession {
return ClientSession()
}
}
class Client {
let session:ClientSession
init(_ session:ClientSession) {
self.session = session
}
}
class Test {
private let createClient = {()->(Client) in
var _aClient = Client(ClientSession.shared())
print("creating client")
return _aClient
}
lazy var aClient:Client = createClient()
func resetClient() {
self.aClient = createClient()
}
}
let test = Test()
test.aClient // creating client
test.aClient
// reset client
test.resetClient() // creating client
test.aClient
答案 5 :(得分:0)
如果目标是重新初始化一个惰性属性,但不一定将其设置为nil(由Phlippie Bosman和Ben Leggiero构建),则可以避免每次读取该值时都进行条件检查:
public struct RLazy<T> {
public var value: T
private var block: () -> T
public init(_ block: @escaping () -> T) {
self.block = block
self.value = block()
}
public mutating func reset() {
value = block()
}
}
要测试:
var prefix = "a"
var test = RLazy { () -> String in
return "\(prefix)b"
}
test.value // "ab"
test.value = "c" // Changing value
test.value // "c"
prefix = "d"
test.reset() // Resetting value by executing block again
test.value // "db"
答案 6 :(得分:0)
Swift 5.1 :
class Game {
private var _scores: [Double]? = nil
var scores: [Double] {
if _scores == nil {
print("Computing scores...")
_scores = [Double](repeating: 0, count: 3)
}
return _scores!
}
func resetScores() {
_scores = nil
}
}
这里是使用方法:
var game = Game()
print(game.scores)
print(game.scores)
game.resetScores()
print(game.scores)
print(game.scores)
这将产生以下输出:
Computing scores...
[0.0, 0.0, 0.0]
[0.0, 0.0, 0.0]
Computing scores...
[0.0, 0.0, 0.0]
[0.0, 0.0, 0.0]
Swift 5.1和Property Wrapper
@propertyWrapper
class Cached<Value: Codable> : Codable {
var cachedValue: Value?
var setter: (() -> Value)?
// Remove if you don't need your Value to be Codable
enum CodingKeys: String, CodingKey {
case cachedValue
}
init(setter: @escaping () -> Value) {
self.setter = setter
}
var wrappedValue: Value {
get {
if cachedValue == nil {
cachedValue = setter!()
}
return cachedValue!
}
set { cachedValue = nil }
}
}
class Game {
@Cached(setter: {
print("Computing scores...")
return [Double](repeating: 0, count: 3)
})
var scores: [Double]
}
我们通过将缓存设置为任意值来重置缓存:
var game = Game()
print(game.scores)
print(game.scores)
game.scores = []
print(game.scores)
print(game.scores)