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


74

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

lazy var aClient:Clinet = {
    var _aClient = Clinet(ClinetSession.shared())
    _aClient.delegate = self
    return _aClient
}()

问题是,在某个时候,我需要重置此aClient变量,以便在ClinetSession.shared()更改后可以再次初始化。但是如果我将类设置为可选的话Clinet?,LLVM在尝试将其设置为时会给我一个错误nil。如果我只是使用重置代码中的某个位置aClient = Clinet(ClinetSession.shared()),它将最终显示为EXEC_BAD_ACCESS

有没有一种方法可以使用lazy并允许自己重置?


只是因为我偶然阅读代码:这是ClinetClinetSession 故意或者是一个错字?
luk2302

1
@ luk2302错字,但毕竟这并不能阻止人们解决我的问题。:)

Answers:


96

懒惰是显式的,仅用于一次初始化。您要采用的模型可能只是按需初始化模型:

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

var _aClient:Client?

现在,只要_aClientnil,它会被初始化并返回。可以通过设置重新初始化_aClient = nil


这很聪明!顺便说一句,我认为我们可能需要使用self._aClientget{}。有时,如果self.缺少,Swift编译器可以找到一个var 。
2014年

6
我曾希望在Swift 2中有更好的东西。您真正想要的是一个带有setter的var,该setter仅允许将其设置为nil,而getter代码仅在该属性为nil时才被调用。就像var aClient:Client {setnil; getnil {aClient = Client(ClientSession.shared())}}如果在将变量设置为nil后得到变量,则将调用getnil。
gnasher729

1
@ gnasher729万岁,斯威夫特3!确切地看到我的回答:)
Ben Leggiero

好的答案...此方法很好地用于设置我最初创建为Lazy var的GMSCircle。谢谢!
BharathRao

如果您使用var _aClient:Client!那么您可以更轻松地在getter中配置_aClient。
malhal

45

因为在Swift 4中行为发生了lazy变化,所以我写了一些structs给出了非常具体的行为,这些行为永远不会在语言版本之间发生变化。我将它们放在BH-1-PD许可下GitHub上:https : //github.com/RougeWare/Swift-Lazy-Patterns

ResettableLazy

这是与此问题相关的一个问题,它为您提供了一种方法来对值进行延迟初始化,缓存该值并销毁它,以便以后可以对它进行延迟重新初始化。

请注意,这需要Swift 5.1!对于Swift 4版本,请参阅该repo的1.1.1版本

它的简单用法非常简单:

@ResettableLazy
var myLazyString = "Hello, lazy!"

print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"

_myLazyString.clear()
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"

myLazyString = "Overwritten"
print(myLazyString) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!"

这将打印:

Hello, lazy!
Hello, lazy!
Hello, lazy!
Hello, lazy!
Overwritten
Hello, lazy!

如果您具有复杂的初始化器逻辑,则可以将其传递给属性包装器:

func makeLazyString() -> String {
    print("Initializer side-effect")
    return "Hello, lazy!"
}

@ResettableLazy(initializer: makeLazyString)
var myLazyString: String

print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"

_myLazyString.clear()
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"

myLazyString = "Overwritten"
print(myLazyString) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!"

您也可以直接使用它(作为属性包装器使用):

var myLazyString = ResettableLazy<String>() {
    print("Initializer side-effect")
    return "Hello, lazy!"
}

print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"

myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"

myLazyString.wrappedValue = "Overwritten"
print(myLazyString.wrappedValue) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!"

这些都将打印:

Initializer side-effect
Hello, lazy!
Hello, lazy!
Initializer side-effect
Hello, lazy!
Hello, lazy!
Overwritten
Initializer side-effect
Hello, lazy!

这个答案已经更新;其原始解决方案不再适用于Swift 4及更高版本。

相反,我建议您使用上面列出的解决方案之一或@PBosman的解决方案

以前,此答案取决于错误行为。无论是老版本的答案,它的行为,为什么这是一个错误的文本和注释中描述斯威夫特错误SR-5172(其中已解决作为2017年7月14日与PR#10911),和很明显这种行为绝不是故意的。

该解决方案是在Swift错误的文本中以及在此答案的历史中,但是由于它是一个漏洞利用程序,因此在Swift 3.2+中不起作用,我建议您要这样做。


2
恕我直言,这是一种比接受的答案更干净的方法。
timgcarlson

1
+1的用法!,但是,如果要利用此模式,则此变量的每个调用方仍必须拆包Optional。例如,print(aClient) // prints "Optional(Clinet)\n"
pxpgraphics

1
我今天早上刚刚在操场上尝试过,上传到这里:gist.github.com/pxpgraphics/4cfb7e02b6be7a583bf5f8a3ccbcd29a 所以,这可能与字符串插值有关:]
pxpgraphics,2016年

1
我认为我不小心使用了此漏洞利用...以为长期存在的代码只是最近才开始导致崩溃(最近更新为Swift 4),这很奇怪。与您接受的解决方案相比,您在编辑中链接的解决方案是否具有强大的优势?接受的解决方案似乎符合我的需求,您链接到的解决方案似乎过于复杂,我看不出它的优势。
杰克T.18年

1
@Ben Leggiero我很感激我的支持:)尽管,正如我在回答中提到的那样,我对我的版本并不完全满意。如果您愿意分享,我很想知道您得到了什么?
Phlippie Bosman '18

7

编辑:按照本·莱吉耶罗的回答,懒惰的变量可以nil在Swift 3中使用。编辑2:似乎nil懒惰的变量不再可用。

非常迟到了,甚至不知道这将是斯威夫特3有关,但在这里不用。David的回答很好,但是如果您想创建许多惰性的不可设置变量,则必须编写大量的代码。我正在尝试创建一个ADT来封装这种行为。到目前为止,这是我得到的:

struct ClearableLazy<T> {
    private var t: T!
    private var constructor: () -> T
    init(_ constructor: @escaping () -> 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

如果有人想从这里捡起它,那就太好了。


1
这基本上就是我现在在Swift 4中所做的
Ben Leggiero

4

这允许将属性设置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
}

3

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)

查看属性包装器替代方法。语法现在更加简洁。
卡拉姆,

2

这里有一些很好的答案。
在许多情况下,确实确实需要重置一个惰性变量。

我认为,您还可以定义一个闭包以创建客户端并使用此闭包重置惰性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

2

如果目标是重新初始化一个惰性属性,但不一定将其设置为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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.