Повторно инициализируйте ленивую инициализированную переменную в Swift

У меня есть переменная, которая инициализировала как:

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 и быть позволенным сбросить себя?

62
задан 20 May 2019 в 02:05

7 ответов

ленивый явно для единовременной инициализации. Модель, которую Вы хотите принять, является, вероятно, просто initialize-on-demand моделью:

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

var _aClient:Client?

Теперь каждый раз, когда _aClient nil, это будет инициализировано и возвращено. Это может быть повторно инициализировано установкой _aClient = nil

85
ответ дан 31 October 2019 в 13:41

Поскольку поведение lazy измененный в Swift 4, я записал некоторым struct с, которые дают очень определенное поведение, которое никогда не должно изменяться между языковыми версиями. Я поместил их на GitHub, под [1 114] лицензия BH-0-PD: https://github.com/RougeWare/Swift-Lazy-Patterns

ResettableLazy

Здесь является одним соответствующим для этого вопроса, который дает Вам, путь к лениво - инициализирует значение, кэш, которые оценивают, и уничтожают его так, это может быть лениво повторно инициализировано позже.

Примечание, что это требует Swift 5.1! Для Swift 4 версии, см. версия 1.1.1 того repo.

простое использование этого очень просто:

@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 ниже поведения была ошибка, описанная в [1 119] ошибка SR-5172 Swift (который был разрешен с 14.07.2017 с [1 120] PR № 10,911 ), и ясно, что это поведение никогда не было намеренным.

решение для Swift 3 ниже по историческим причинам, но потому что это - использование ошибки, которое не работает в Swift 3.2 + , я рекомендую сделать не , делают это:

<час>

я не уверен точно, когда это было добавлено, но с [1 141] Swift 3, можно просто сделать свойство способным нолем: lazy var aClient:Client! = { var _aClient = Client(ClinetSession.shared()) _aClient.delegate = self return _aClient }()
// ...
aClient = nil
Теперь, в следующий раз, когда Вы называете aClient после установки его к nil, это будет повторно инициализировано. Примечание---, что, хотя это является теперь технически дополнительным, каждый раз, Вы пытаетесь считать его, это, как гарантируют, будет иметь значение во время выполнения. Вот почему я использую !, здесь, потому что это всегда - безопасный вызов и никогда не будет , читает как [1 111], но это может быть , устанавливает на [1 112].

42
ответ дан 31 October 2019 в 13:41

РЕДАКТИРОВАНИЕ: Согласно ответу Ben Leggiero, ленивый Вар может быть nil способен в Swift 3. РЕДАКТИРОВАНИЕ 2: Походит nil, способный ленивый Вар больше не.

Очень поздно стороне, и даже уверенный, если это будет релевантно в Swift 3, но здесь идет. Ответ David хорош, но если Вы хотите создать многих ленивый способный нолем Вар, необходимо будет записать довольно значительный блок кода. Я пытаюсь создать 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.

, Если бы кто-то испытывает желание брать его отсюда, который был бы потрясающим.

6
ответ дан 31 October 2019 в 13:41

Это позволяет устанавливать свойство на 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
ответ дан 31 October 2019 в 13:41

Здесь существуют некоторые хорошие ответы.
Сброс ленивого var действительно, желателен в большом количестве случаев.

я думаю, можно также определить закрытие, чтобы создать клиент и сбросить ленивый 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
1
ответ дан 31 October 2019 в 13:41

Если цель состоит в том, чтобы повторно инициализировать ленивое свойство, но не обязательно установить, она к нолю, Создающему от 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"
0
ответ дан 31 October 2019 в 13:41

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 и Обертка Свойства

@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)
0
ответ дан 31 October 2019 в 13:41

Другие вопросы по тегам:

Похожие вопросы: