Изменяемое свойство, запрещающее прямое назначение - Swift - PullRequest
0 голосов
/ 25 мая 2019

Изменчивая изменчивость в исключении присваивания

Swift 5.0 - Xcode 10.2.1

У меня есть класс со свойством, которое я хотел бы изменить, за исключением того, что я не хочу, чтобы свойство было напрямую назначено с помощью оператора =. Возьмите следующий пример:

class Foo {
    var counter = Counter()

    init() { }
}

struct Counter {
    private(set) var n = 0

    let interval: Int

    mutating func increment() {
        n += interval
    }

    init(by interval: Int = 1) {
        self.interval = interval
    }
}

Что я хочу получить:

let foo = Foo()
foo.counter.increment()

То, что я не хочу, чтобы мне разрешили:

let fasterCounter = Counter(by: 10)
let foo = Foo()
foo.counter = fasterCounter

Примечание: Хотя я знаю, что могу сделать counter a private(set) var, а также создать и сделать функцию incrementCounter() в Foo, которая увеличивает ее, я хотел бы иметь возможность доступа к методам мутации напрямую через переменную counter поскольку он захламляет класс и раздражает делать для типов со многими мутирующими методами. Кроме того, я знаю, что я также могу сделать counter константой и Counter классом, но мне нужна семантика типа значения для свойства.

1 Ответ

1 голос
/ 25 мая 2019

Вы правильно перечислили все доступные опции. Там нет никакого дополнительного трюка, о котором вы не знаете, если это то, что вы просите. Это просто недостаток Swift, когда (как это часто бывает) вы хотите использовать вспомогательную структуру.

Я постоянно сталкиваюсь с этой проблемой в своем собственном коде. Например:

final class ViewController: UIViewController {
    lazy var state = Mover(owner:self)

Это не то, что я действительно хотел сказать, по той же причине, что и вы. Mover должен быть изменчивым и являться структурой. Так что в теории другой Mover мог бы быть назначен на мой state поверх этого. Мне просто нужно заключить с самим собой договор, чтобы не делать этого, что, увы, не так легко осуществить.

Если все, что вы действительно хотите сделать, это предотвратить замену счетчика с другим интервалом, вы можете использовать наблюдатель didSet (хотя, конечно, принудительное выполнение выполняется во время выполнения, а не во время компиляции):

var counter = Counter() {
    didSet {
        if oldValue.interval != counter.interval {
            fatalError("hey")
        }
    }
}

Но вы можете себе представить, что это может быть очень сложно. Каждая мутация заменяет другой счетчик, поэтому меры предосторожности, чтобы убедиться, что эта замена является «просто» результатом мутации, могут быть довольно сложными. Также, если вы собираетесь использовать этот подход, вы, вероятно, захотите навязать самой Counter работу по выяснению того, что является «легальной» мутацией. В этом простом случае мы могли бы использовать равенство:

class Foo {
    var counter = Counter() {
        didSet {
            if oldValue != counter {
                fatalError("hey")
            }
        }
    }
    init() { }
}

struct Counter : Equatable {
    static func ==(lhs:Counter,rhs:Counter) -> Bool {
        return rhs.interval == lhs.interval
    }
    // the rest as before....
}
...