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

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



답변

(새로운 정보를 추가하기 위해 편집 됨) : KVO를 사용하지 않고 Combine 프레임 워크를 사용하여 원하는 것을 달성 할 수 있는지 고려하십시오.

예, 아니오 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. 키 경로에 대한 관찰자를 추가하고 observeValueForKeyPath:ofObject:change:context:메소드를 대체하고 에서 관찰자를 제거하십시오 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)
        }
    }

[참고로이 KVO 토론은 Swift 3에 맞게 조정 된 Cocoa 및 Objective-C와 함께 Swift 사용 가이드 에서 제거 되었지만 여전히이 답변의 맨 위에 설명 된대로 작동합니다.]


Swift는 자체 고유 속성 관찰 시스템을 가지고 있지만 자체 속성을 관찰 할 때 자체 코드를 지정하는 클래스입니다. 반면 KVO는 다른 클래스의 동적 속성에 대한 변경 사항을 관찰하도록 등록하도록 설계되었습니다.


답변

예와 아니오 :

  • , Swift에서 동일한 이전 KVO API를 사용하여 Objective-C 객체를 관찰 할 수 있습니다. 에서 상속되는 Swift 객체의 속성을
    관찰 할 수도 있습니다 .
    그러나 … 아니요 스위프트 기본 관측 시스템을 기대할 수있는 강력한 형식은 아닙니다. Cocoa 및 Objective-C와 함께 Swift 사용 | 주요 가치 관찰dynamicNSObject

  • 아니오 , 현재 임의의 Swift 객체에 대한 내장 값 관찰 시스템이 없습니다.

  • , 내장이있는 속성 관찰자 강력한 형식있다.
    그러나 … 아니요. 객체 자체 속성 만 관찰 할 수 있고 중첩 된 관찰 ( “키 경로”)을 지원하지 않으므로 명시 적으로 구현해야하므로 KVO가 아닙니다.
    스위프트 프로그래밍 언어 | 부동산 관찰자

  • , 명시 적 값 관찰을 구현할 수 있습니다. 강력한 관찰이 가능하며 다른 객체에서 여러 핸들러를 추가 할 수 있으며 중첩 / “키 경로”도 지원할 수 있습니다.
    그러나 … 아니요 . 관찰 가능한 것으로 구현하는 속성에서만 작동하기 때문에 KVO가 아닙니다.
    이러한 값 관찰을 구현하기위한 라이브러리는 다음에서 찾을 수 있습니다.
    Observable-Swift-KVO for Swift-값 관찰 및 이벤트


답변

여기에 약간의 도움이 될 수 있습니다. 나는 인스턴스가있는 경우 model클래스의 Model속성을 name하고 state난과 그 특성을 관찰 할 수있다 :

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)

이러한 속성을 변경하면 다음에 대한 호출이 트리거됩니다.

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

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


답변

예.

KVO에는 동적 디스패치가 필요하므로 dynamic메소드, 속성, 첨자 또는 이니셜 라이저에 수정자를 추가하기 만하면됩니다 .

dynamic var foo = 0

dynamic선언에 대한 참조 동적를 통해 전송 및 액세스 할 것이라는 점을 수정 보장하지만 objc_msgSend.


답변

Rob의 답변 외에도. 이 클래스는에서 상속해야하며 NSObject속성 변경을 트리거하는 3 가지 방법이 있습니다.

사용 setValue(value: AnyObject?, forKey key: String)에서NSKeyValueCoding

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

사용 willChangeValueForKeydidChangeValueForKey에서NSKeyValueObserving

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

사용하십시오 dynamic. 스위프트 타입 호환성 참조

동적 수정자를 사용하여 메소드의 구현을 동적으로 대체하는 키-값 관찰과 같은 API를 사용하는 경우 Objective-C 런타임을 통해 멤버에 대한 액세스를 동적으로 디스패치하도록 요구할 수 있습니다.

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

그리고 getter 및 setter 속성이 사용될 때 호출됩니다. KVO로 작업 할 때 확인할 수 있습니다. 이것은 계산 된 속성의 예입니다

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
        }
    }
}


답변

개요

그것은 사용 가능한 Combine사용하지 않고 NSObjectObjective-C

가용성 : iOS 13.0+ , macOS 10.15+, tvOS 13.0+, watchOS 6.0+, Mac Catalyst 13.0+,Xcode 11.0+

참고 : 값 유형이 아닌 클래스에만 사용해야합니다.

암호:

스위프트 버전 : 5.1.2

import Combine //Combine Framework

//Needs to be a class doesn't work with struct and other value types
class Car {

    @Published var price : Int = 10
}

let car = Car()

//Option 1: Automatically Subscribes to the publisher

let cancellable1 = car.$price.sink {
    print("Option 1: value changed to \($0)")
}

//Option 2: Manually Subscribe to the publisher
//Using this option multiple subscribers can subscribe to the same publisher

let publisher = car.$price

let subscriber2 : Subscribers.Sink<Int, Never>

subscriber2 = Subscribers.Sink(receiveCompletion: { print("completion \($0)")}) {
    print("Option 2: value changed to \($0)")
}

publisher.subscribe(subscriber2)

//Assign a new value

car.price = 20

산출:

Option 1: value changed to 10
Option 2: value changed to 10
Option 1: value changed to 20
Option 2: value changed to 20

보내다: