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

Проверка того, изменилось ли значение с помощью KVO в Swift 3

Я хотел бы знать, когда изменяется набор свойств объекта Swift. Раньше я реализовал это в Objective-C, но мне сложно преобразовать его в Swift.

Мой предыдущий Objective-C код:

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    if (![change[@"new"] isEqual:change[@"old"]])
        [self edit];
}

Мой первый проход в решении Swift:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if change?[.newKey] != change?[.oldKey] {    // Compiler: "Binary operator '!=' cannot be applied to two 'Any?' operands"
        edit()
    }
}

Однако компилятор жалуется: "Двоичный оператор '! =' не может применяться к двум" Любые? ". операнды"

Моя вторая попытка:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSObject {
        if let oldValue = change?[.oldKey] as? NSObject {
            if !newValue.isEqual(oldValue) {
                edit()
            }
        }
    }
}

Но, думая об этом, я не думаю, что это будет работать на примитивы быстрых объектов, таких как Int, которые (я полагаю) не наследуют от NSObject и в отличие от версии Objective-C не будут помещены в NSNumber когда они помещаются в словарь изменений.

Итак, вопрос заключается в том, как сделать кажущуюся легкую задачу определить, действительно ли значение изменяется с помощью KVO в Swift3?

Также, вопрос бонуса, как я могу использовать переменную 'объекта'? Это не позволит мне изменить имя и, конечно, не любит переменные с пробелами в них.

4b9b3361

Ответ 1

Ты сказал:

Но, думая об этом, я не думаю, что это будет работать на примитивы быстрых объектов, таких как Int, которые (я предполагаю) не наследуются от NSObject и в отличие от версии Objective-C не будут помещаться в NSNumber, если он помещен в словарь изменений.

Собственно, если вы посмотрите на эти значения, если наблюдаемое свойство равно Int, оно приходит через словарь как NSNumber.

Итак, вы можете остаться в мире NSObject:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSObject {
        if let oldValue = change?[.oldKey] as? NSObject {
            if !newValue.isEqual(oldValue as Any) {
                edit()
            }
        }
    }
}

Или используйте их как NSNumber:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSNumber {
        if let oldValue = change?[.oldKey] as? NSNumber {
            if newValue.intValue != oldValue.intValue {
                edit()
            }
        }
    }
}

Или, если бы это было значением Int для некоторого свойства dynamic некоторого класса Swift, я бы пошел дальше и произнес его как Int:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? Int {
        if let oldValue = change?[.oldKey] as? Int {
            if newValue != oldValue {
                edit()
            }
        }
    }
}

Или, более кратко:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue {
        edit()
    }
}

Вы спросили:

Также, вопрос бонуса, как я могу использовать переменную of object? Это не позволит мне изменить имя и, конечно, не любит переменные с пробелами в них.

of - это внешняя метка для этого параметра (используется, если вы вызываете этот метод, в этом случае ОС вызывает это для нас, поэтому мы не используем эту внешнюю метку, не указанную в сигнатуре метода). object является внутренней меткой (используется внутри самого метода). У Swift была возможность для внешних и внутренних меток для параметров на некоторое время, но она была действительно включена в API по сравнению с Swift 3.

В терминах использования этого параметра change вы используете его, если вы наблюдаете свойства более чем одного объекта, и если эти объекты нуждаются в различной обработке на KVO, например:

foo.addObserver(self, forKeyPath: "bar", options: [.new, .old], context: &observerContext)
baz.addObserver(self, forKeyPath: "qux", options: [.new, .old], context: &observerContext)

И затем:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if (object as? Foo) == foo {
        // handle `foo` related notifications here
    }
    if (object as? Baz) == baz {
        // handle `baz` related notifications here
    }
}

В стороне, я обычно рекомендую использовать context, например, иметь private var:

private var observerContext = 0

Затем добавьте наблюдателя в этот контекст:

foo.addObserver(self, forKeyPath: "bar", options: [.new, .old], context: &observerContext)

И пусть мой observeValue убедитесь, что это его context, а не один, установленный его суперклассом:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue {
        edit()
    }
}

Ответ 2

Моя первоначальная "вторая попытка" содержит ошибку, которая не сможет определить, когда значение изменяется на или из нуля. Первая попытка может быть исправлена, если осознать, что все значения "изменения" - это NSObject:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    let oldValue: NSObject? = change?[.oldKey] as? NSObject
    let newValue: NSObject? = change?[.newKey] as? NSObject
    if newValue != oldValue {
        edit()
    }
}

или более краткий вариант, если вы предпочитаете:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if change?[.newKey] as? NSObject != change?[.oldKey] as? NSObject {
        edit()
    }
}