Подтвердить что ты не робот

Повторно инициализировать ленивую инициализированную переменную в 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 и может быть сброшен сам?

4b9b3361

Ответ 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

ResettableLazy

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

Обратите внимание, что для этого требуется 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)