在Swift中重新初始化一个惰性初始化变量

时间:2014-08-01 03:50:26

标签: swift

我有一个初始化为的变量:

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并允许自行重置?

7 个答案:

答案 0 :(得分:78)

lazy明确表示只进行一次初始化。您要采用的模型可能只是初始化按需模型:

var aClient:Client {
    if(_aClient == nil) {
        _aClient = Client(ClientSession.shared())
    }
    return _aClient!
}

var _aClient:Client?

现在只要_aClientnil,它就会被初始化并返回。可以通过设置_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

以下解决方案不再适用于Swift 4!

相反,我建议您使用上面列出的解决方案之一,或@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 = nil
 现在,下次在将其设置为nil后调用aClient时,它将重新初始化。  ---  请注意,虽然它现在在技术上是可选的,但每次尝试读取它时,都保证具有运行时值。这就是我在这里使用!的原因,因为它始终是一个安全的调用,永远不会读取 nil,但它可以 set nil

答案 2 :(得分:6)

编辑:根据Ben Leggiero的回答,在Swift 3中,懒惰的变量可以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)