Programing

Swift에서 키-값 관찰 (KVO)이 가능합니까?

crosscheck 2020. 6. 3. 21:18
반응형

Swift에서 키-값 관찰 (KVO)이 가능합니까?


그렇다면 Objective-C에서 키-값 관찰을 사용할 때 달리 존재하지 않는 주요 차이점이 있습니까?


예, 아니오 KVO는 NSObject 서브 클래스에서 항상 그렇듯이 작동합니다. NSObject를 서브 클래스하지 않는 클래스에는 작동하지 않습니다. 스위프트는 (현재는 최소한) 고유의 관측 시스템을 가지고 있지 않습니다.

(KVO가 작동하도록 ObjC로 다른 속성을 노출하는 방법에 대한 의견 참조)

전체 예 Apple 설명서참조하십시오 .


Swift에서 KVO를 사용할 수 있지만 하위 클래스의 dynamic속성 에만 사용할 수 있습니다 NSObject. 클래스 bar속성 을 관찰하고 싶다고 생각하십시오 Foo. 스위프트 4에서 지정 bar으로 dynamic당신의 재산 NSObject서브 클래스 :

class Foo: NSObject {
    @objc dynamic var bar = 0
}

그런 다음 등록 정보의 변경 사항을 관찰하도록 등록 할 수 있습니다 bar. Swift 4 및 Swift 3.2에서는 Swift에서 키-값 관찰 사용에 설명 된대로 크게 단순화되었습니다 .

class MyObject {
    private var token: NSKeyValueObservation

    var objectToObserve = Foo()

    init() {
        token = objectToObserve.observe(\.bar) { [weak self] object, change in  // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
            print("bar property is now \(object.bar)")
        }
    }
}

Swift 4에서는 백 슬래시 문자를 사용 \.bar하여 키 경로를 강력하게 입력했습니다 (이는 bar관찰되는 객체 속성에 대한 키 경로입니다 ). 또한 완료 클로저 패턴을 사용하기 때문에 관찰자를 수동으로 제거 할 필요가 없으며 ( token범위를 벗어나면 관찰자가 제거됩니다) super키가 아닌 경우 구현 호출에 대해 걱정할 필요 가 없습니다 시합. 클로저는이 특정 옵저버가 호출 될 때만 호출됩니다. 자세한 내용은 WWDC 2017 비디오, Foundation의 새로운 기능을 참조하십시오 .

Swift 3에서 이것을 관찰하기 위해 조금 더 복잡하지만 Objective-C에서하는 것과 매우 유사합니다. 즉, observeValue(forKeyPath keyPath:, of object:, change:, context:)(a) super인스턴스가 관찰하도록 등록한 것이 아니라 컨텍스트를 처리하는지 확인합니다 . (b) 필요에 따라 처리하거나 super구현 에 전달합니다 . 적절한 경우 관찰자로 자신을 제거하십시오. 예를 들어, 할당 해제시 관찰자를 제거 할 수 있습니다.

스위프트 3에서 :

class MyObject: NSObject {
    private var observerContext = 0

    var objectToObserve = Foo()

    override init() {
        super.init()

        objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
    }

    deinit {
        objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), 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
        }

        // do something upon notification of the observed object

        print("\(keyPath): \(change?[.newKey])")
    }

}

Objective-C로 표현할 수있는 특성 만 관찰 할 수 있습니다. 따라서 제네릭, Swift struct유형, Swift enum유형 등을 관찰 할 수 없습니다 .

Swift 2 구현에 대한 논의는 아래의 원래 답변을 참조하십시오.


서브 클래스로 dynamicKVO를 달성하기 위해 키워드를 사용하는 방법 NSObjectCocoa 및 Objective-C와 함께 Swift 사용 안내서코코아 설계 규칙 채택 장의 키-값 관찰 섹션에 설명되어 있습니다 .

키-값 관찰은 다른 객체의 지정된 속성에 대한 변경 사항을 객체에 통보 할 수있는 메커니즘입니다. 클래스가 클래스에서 상속되는 한 Swift 클래스에서 키-값 관찰을 사용할 수 있습니다 NSObject. 이 세 단계를 사용하여 Swift에서 키-값 관찰을 구현할 수 있습니다.

  1. dynamic관찰하려는 속성에 수정자를 추가하십시오 . 에 대한 자세한 내용은 동적 디스패치 요청을dynamic 참조하십시오 .

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
    
  2. 글로벌 컨텍스트 변수를 작성하십시오.

    private var myContext = 0
    
  3. Add an observer for the key-path, and override the observeValueForKeyPath:ofObject:change:context: method, and remove the observer in deinit.

    class MyObserver: NSObject {
        var objectToObserve = MyObjectToObserve()
        override init() {
            super.init()
            objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
        }
    
        override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
            if context == &myContext {
                if let newValue = change?[NSKeyValueChangeNewKey] {
                    print("Date changed: \(newValue)")
                }
            } else {
                super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            }
        }
    
        deinit {
            objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
        }
    }
    

[Note, this KVO discussion has subsequently been removed from the Using Swift with Cocoa and Objective-C guide, which has been adapted for Swift 3, but it still works as outlined at the top of this answer.]


It's worth noting that Swift has its own native property observer system, but that's for a class specifying its own code that will be performed upon observation of its own properties. KVO, on the other hand, is designed to register to observe changes to some dynamic property of some other class.


Both yes and no:

  • Yes, you can use the same old KVO APIs in Swift to observe Objective-C objects.
    You can also observe dynamic properties of Swift objects inheriting from NSObject.
    But... No it's not strongly typed as you could expect Swift native observation system to be.
    Using Swift with Cocoa and Objective-C | Key Value Observing

  • No, currently there is no builtin value observation system for arbitrary Swift objects.

  • Yes, there are builtin Property Observers, which are strongly typed.
    But... No they are not KVO, since they allow only for observing of objects own properties, don't support nested observations ("key paths"), and you have to explicitly implement them.
    The Swift Programming Language | Property Observers

  • Yes, you can implement explicit value observing, which will be strongly typed, and allow for adding multiple handlers from other objects, and even support nesting / "key paths".
    But... No it will not be KVO since it will only work for properties which you implement as observable.
    You can find a library for implementing such value observing here:
    Observable-Swift - KVO for Swift - Value Observing and Events


An example might help a little here. If I have an instance model of class Model with attributes name and state I can observe those attributes with:

let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior])

model.addObserver(self, forKeyPath: "name", options: options, context: nil)
model.addObserver(self, forKeyPath: "state", options: options, context: nil)

Changes to these properties will trigger a call to:

override func observeValueForKeyPath(keyPath: String!,
    ofObject object: AnyObject!,
    change: NSDictionary!,
    context: CMutableVoidPointer) {

        println("CHANGE OBSERVED: \(change)")
}

Yes.

KVO requires dynamic dispatch, so you simply need to add the dynamic modifier to a method, property, subscript, or initializer:

dynamic var foo = 0

The dynamic modifier ensures that references to the declaration will be dynamically dispatched and accessed through objc_msgSend.


Currently Swift does not support any built in mechanism for observing property changes of objects other than 'self', so no, it does not support KVO.

However, KVO is such a fundamental part of Objective-C and Cocoa that it seems quite likely that it will be added in the future. The current documentation seems to imply this:

Key-Value Observing

Information forthcoming.

Using Swift with Cocoa and Objective-C


In addition to Rob's answer. That class must inherit from NSObject, and we have 3 ways to trigger property change

Use setValue(value: AnyObject?, forKey key: String) from NSKeyValueCoding

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        setValue(NSDate(), forKey: "myDate")
    }
}

Use willChangeValueForKey and didChangeValueForKey from NSKeyValueObserving

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        willChangeValueForKey("myDate")
        myDate = NSDate()
        didChangeValueForKey("myDate")
    }
}

Use dynamic. See Swift Type Compatibility

You can also use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime if you’re using APIs like key–value observing that dynamically replace the implementation of a method.

class MyObjectToObserve: NSObject {
    dynamic var myDate = NSDate()
    func updateDate() {
        myDate = NSDate()
    }
}

And property getter and setter is called when used. You can verify when working with KVO. This is an example of computed property

class MyObjectToObserve: NSObject {
    var backing: NSDate = NSDate()
    dynamic var myDate: NSDate {
        set {
            print("setter is called")
            backing = newValue
        }
        get {
            print("getter is called")
            return backing
        }
    }
}

One important thing to mention is that after updating your Xcode to 7 beta you might be getting the following message: "Method does not override any method from its superclass". That's because of the arguments' optionality. Make sure that your observation handler looks exactly as follows:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)

This may be prove helpful to few people -

// MARK: - KVO

var observedPaths: [String] = []

func observeKVO(keyPath: String) {
    observedPaths.append(keyPath)
    addObserver(self, forKeyPath: keyPath, options: [.old, .new], context: nil)
}

func unObserveKVO(keyPath: String) {
    if let index = observedPaths.index(of: keyPath) {
        observedPaths.remove(at: index)
    }
    removeObserver(self, forKeyPath: keyPath)
}

func unObserveAllKVO() {
    for keyPath in observedPaths {
        removeObserver(self, forKeyPath: keyPath)
    }
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let keyPath = keyPath {
        switch keyPath {
        case #keyPath(camera.iso):
            slider.value = camera.iso
        default:
            break
        }
    }
}

I had used KVO in this way in Swift 3. You can use this code with few changes.


Another example for anyone who runs into a problem with types such as Int? and CGFloat?. You simply set you class as a subclass of NSObject and declare your variables as follows e.g:

class Theme : NSObject{

   dynamic var min_images : Int = 0
   dynamic var moreTextSize : CGFloat = 0.0

   func myMethod(){
       self.setValue(value, forKey: "\(min_images)")
   }

}

참고URL : https://stackoverflow.com/questions/24092285/is-key-value-observation-kvo-available-in-swift

반응형