Есть ли способ сделать наблюдаемое перечисление в Swift (KVO)

Я пытаюсь использовать RxSwift для привязки в MVVM. У меня Enum:

enum Color : Int {
    case Red = 0, Green
}

и класс для теста

class Test : NSObject {
    var color: Color = .Red
    dynamic var test: String? {
        didSet {
            print("didSet \(test)")
        }
    }
}

И хочу наблюдать за такими изменениями, как:

test.rx_observe(Color.self, "color").subscribeNext { (color) -> Void in
     print("Observer \(color)")
}.addDisposableTo(bag)

Но прога гоняется с *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<RDProject.Test 0x7ff373513020> addObserver:<RxCocoa.KVOObserver 0x7ff37351a420> forKeyPath:@"color" options:5 context:0x0] was sent to an object that is not KVC-compliant for the "color" property.'

Код для простых String работ:

test.rx_observe(String.self, "test").subscribeNext { string in
     print("Observer \(string)")
}.addDisposableTo(bag)

test.test = "1"
test.test = "2"

Я нашел здесь, чтобы сделать класс унаследованным не из NSObject Я должен сделать это dynamic, но я не могу сделать Enum динамическим. Есть ли способ сделать Enum наблюдаемым?


person katleta3000    schedule 16.03.2016    source источник


Ответы (3)


Вам не нужно использовать KVO для этой задачи. Просто используйте BehaviorSubject вот так:

Создайте частное поле, подобное этому:

let colorSubject = BehaviorSubject<Color?>(value: nil)

Тогда у вас есть такое свойство, которое информирует BehaviorSubject о том, что значение действительно изменилось.

var color : Color? {
    didSet {
        colorSubject.onNext(color)
    }
}

Чтобы подписаться на любое изменение, используйте оператор, эквивалентный этому:

let disposable = colorSubject.subscribeNext { (color: Color?) -> Void in
    // Do something with it.
}
person Sebastian Krogull    schedule 16.03.2016

Поскольку ваше перечисление имеет тип Int, вы можете сделать его совместимым с objective-c, пометив его с помощью @objc. Это заставит компилятор правильно пометить свойство как dynamic. Чтобы свойство было KVO-совместимым, его также необходимо пометить с помощью @objc.

@objc enum Color : Int {
    case Red = 0, Green
}

class Test : NSObject {
    @objc dynamic var color: Color = .Red
    dynamic var test: String? {
        didSet {
            print("didSet \(test)")
        }
    }
}
person tomahh    schedule 16.03.2016
comment
Привет. Я использую Swift 5 и пробовал это решение. К сожалению, он вылетает с Fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath ... . Не могли бы вы подтвердить, что это все еще работает? - person Jeroen; 11.10.2019
comment
Он действительно дает сбой, но пометка поля с помощью @objc устраняет сбой и снова заставляет эту функцию работать. Я обновил свой ответ, чтобы отразить это. Надеюсь, это поможет :) - person tomahh; 14.10.2019
comment
Спасибо за ответ! --- Это действительно решает проблему. Прежде всего, это перечисление типа Error, изменение его на Int - это первый шаг. --- Затем аннотирование перечисления и свойства с помощью @objc - это последний шаг для выполнения этой работы. --- И тогда у меня все еще есть проблема, это свойство MyEnumType?, поэтому Необязательный. Это не может быть представлено в Objective-C. Я не думаю, что это можно решить. Но вы ответили на мой вопрос, и это работает :-) - person Jeroen; 14.10.2019

Я могу предложить только сделать прокси-переменную и использовать на ней KVO.

class Model: NSObject {

    enum Color: Int {
        case Red = 0, Green
    }

    dynamic var colorRaw: Int?
    var color: Color = .Red {
        didSet {
            colorRaw = color.rawValue
        }
    }

}

Подробнее здесь - https://christiantietze.de/posts/2015/05/observing-enum-swift-kvo/

person Arsen    schedule 16.03.2016