Повторно инициализировать ленивую инициализированную переменную в 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
и может быть сброшен сам?
Ответы
Ответ 1
Ленивый явно для единовременной только инициализации. Модель, которую вы хотите принять, является, вероятно, просто моделью инициализации по требованию:
var aClient:Client {
if(_aClient == nil) {
_aClient = Client(ClientSession.shared())
}
return _aClient!
}
var _aClient:Client?
Теперь, когда _aClient
равен nil
, он будет инициализирован и возвращен. Его можно _aClient = nil
инициализировать, установив _aClient = nil
Ответ 2
Поскольку поведение lazy
изменилось в Swift 4, я написал несколько struct
, которые дают очень специфическое поведение, которое никогда не должно меняться в разных языковых версиях. Я разместил их на GitHub под лицензией BH-0-PD: https://github.com/RougeWare/Swift-Lazy-Patterns
Вот вопрос, относящийся к этому вопросу, который дает вам возможность лениво-инициализировать значение, кэшировать это значение и уничтожить его, чтобы его можно было потом без проблем переинициализировать.
Обратите внимание, что для этого требуется Swift 5.1! Для версии Swift 4 см. версию 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
Приведенное ниже поведение было ошибкой, описанной в ошибке Swift 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
Теперь, когда вы в следующий раз вызовете aClient после установки его в nil
, он будет повторно инициализирован.
---
Обратите внимание, что, хотя теперь это технически необязательно, каждый раз, когда вы пытаетесь его прочитать, оно гарантированно будет иметь значение времени выполнения. Вот почему я здесь использую !
, потому что это всегда безопасный вызов и никогда не будет читаться как nil
, но его можно установить на nil
.
Ответ 3
РЕДАКТИРОВАТЬ: Согласно ответу Бена Легжеро, ленивые переменные могут быть nil
в Swift 3. РЕДАКТИРОВАТЬ 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
.
Если кто-то захочет забрать его отсюда, это было бы здорово.
Ответ 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
}
Ответ 5
Вы можете сбросить ленивое значение. Попытка сделать это как можно более простым в целях иллюстрации.
struct ResettableLazy {
lazy var value: Int = {
9
}()
}
var v = ResettableLazy()
v.value // 9
v.value = 3 // could also be a reset function on struct
v.value // 3
Ответ 6
Здесь есть несколько хороших ответов.
Сброс lazy var действительно желателен во многих случаях.
Я думаю, вы также можете определить закрытие для создания клиента и reset 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
Ответ 7
Если цель состоит в том, чтобы повторно инициализировать ленивое свойство, но не обязательно устанавливать его равным 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"
Ответ 8
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)