그렇다면 Objective-C에서 키-값 관찰을 사용할 때 달리 존재하지 않는 주요 차이점이 있습니까?
그렇다면 Objective-C에서 키-값 관찰을 사용할 때 달리 존재하지 않는 주요 차이점이 있습니까?
답변:
(새로운 정보를 추가하기 위해 편집 됨) : KVO를 사용하지 않고 Combine 프레임 워크를 사용하여 원하는 것을 달성 할 수 있는지 고려하십시오.
예, 아니오 KVO는 NSObject 서브 클래스에서 항상 그렇듯이 작동합니다. NSObject를 서브 클래스하지 않는 클래스에는 작동하지 않습니다. 스위프트는 (현재는 최소한) 고유의 관측 시스템을 가지고 있지 않습니다.
(KVO가 작동하도록 ObjC로 다른 속성을 노출하는 방법에 대한 의견 참조)
전체 예 는 Apple 설명서 를 참조하십시오 .
dynamic
모든 Swift 클래스 에서 키워드를 사용하여 KVO 지원을 활성화 할 수 있습니다 .
dynamic
키워드는 키-값을 관찰 가능하게하려는 속성으로 이동합니다.
dynamic
키워드에 대한 설명 은 Apple Developer Library의 Cocoa 및 Objective-C와 함께 Swift 사용 섹션 에서 찾을 수 있습니다 .
dynamic
키워드를 사용하십시오 ( 클래스 자체 의 키워드가 아님). 이것은 나를 위해 일했다! dynamic
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 구현에 대한 논의는 아래의 원래 답변을 참조하십시오.
서브 클래스로 dynamic
KVO를 달성하기 위해 키워드를 사용하는 방법 NSObject
은 Cocoa 및 Objective-C와 함께 Swift 사용 안내서 의 코코아 설계 규칙 채택 장의 키-값 관찰 섹션에 설명되어 있습니다 .
키-값 관찰은 다른 객체의 지정된 속성에 대한 변경 사항을 객체에 통보 할 수있는 메커니즘입니다. 클래스가 클래스에서 상속되는 한 Swift 클래스에서 키-값 관찰을 사용할 수 있습니다
NSObject
. 이 세 단계를 사용하여 Swift에서 키-값 관찰을 구현할 수 있습니다.
dynamic
관찰하려는 속성에 수정자를 추가하십시오 . 에 대한 자세한 내용은 동적 디스패치 요청을dynamic
참조하십시오 .class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
글로벌 컨텍스트 변수를 작성하십시오.
private var myContext = 0
키 경로에 대한 관찰자를 추가하고
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는 다른 클래스의 동적 속성에 대한 변경 사항을 관찰하도록 등록하도록 설계되었습니다.
myContext
여러 속성 의 목적은 무엇 이며 어떻게 여러 속성을 관찰합니까?
context
포인터를 제공 할 수도 있습니다 . context
포인터 observeValueForKeyPath:ofObject:change:context:
는 호출 될 때 옵저버에게 제공 됩니다. context
포인터는 C 포인터 또는 객체 참조 일 context
수 있습니다 . 포인터는 관찰중인 변경 사항을 결정하거나 관찰자에게 다른 데이터를 제공하기 위해 고유 식별자로 사용됩니다. "
options
비워 두면 change
이전 또는 새 값이 포함되지 않음을 의미합니다 (예 : 개체 자체를 참조하여 새 값을 직접 얻을 수 있음). 당신은 단지 지정하는 경우 .new
가 아니라 .old
, 그 의미 change
만이 새로운 가치,하지만 이전 값을 (예를 들어, 당신이 종종 이전 값이 무엇인지에 대해 걱정하지 않는다,하지만 새로운 값에 대해 신경)이 포함됩니다. observeValueForKeyPath
이전 값과 새 값을 모두 전달 해야하는 경우을 지정하십시오 [.new, .old]
. 결론 options
은 change
사전에 포함 된 내용 만 지정합니다 .
예와 아니오 :
예 , Swift에서 동일한 이전 KVO API를 사용하여 Objective-C 객체를 관찰 할 수 있습니다. 에서 상속되는 Swift 객체의 속성을
관찰 할 수도 있습니다 .
그러나 ... 아니요 스위프트 기본 관측 시스템을 기대할 수있는 강력한 형식은 아닙니다. Cocoa 및 Objective-C와 함께 Swift 사용 | 주요 가치 관찰dynamic
NSObject
아니오 , 현재 임의의 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)")
}
Rob의 답변 외에도. 이 클래스는에서 상속해야하며 NSObject
속성 변경을 트리거하는 3 가지 방법이 있습니다.
사용 setValue(value: AnyObject?, forKey key: String)
에서NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
사용 willChangeValueForKey
및 didChangeValueForKey
에서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
사용하지 않고 NSObject
나Objective-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
현재 Swift는 'self'이외의 객체의 속성 변경을 관찰하기위한 내장 메커니즘을 지원하지 않으므로 KVO를 지원하지 않습니다.
그러나 KVO는 Objective-C 및 Cocoa의 기본 부분이므로 향후 추가 될 가능성이 높습니다. 현재 문서는 이것을 암시하는 것으로 보입니다.
키-값 관찰
앞으로의 정보.
한 가지 중요한 점은 Xcode 를 7 베타로 업데이트 한 후 "메소드는 슈퍼 클래스의 메소드를 대체하지 않습니다"라는 메시지가 표시 될 수 있습니다 . 그것은 논증의 선택성 때문입니다. 관찰 처리기가 다음과 정확히 같은지 확인하십시오.
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)
이것은 소수의 사람들에게 도움이 될 수 있습니다.
// 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
}
}
}
Swift 3에서 이런 식으로 KVO를 사용했습니다.이 코드를 약간만 변경하면 사용할 수 있습니다.
Int와 같은 유형에 문제가있는 사람을위한 또 다른 예? 그리고 CGFloat ?. 클래스를 NSObject의 하위 클래스로 설정하고 변수를 다음과 같이 선언하면됩니다.
class Theme : NSObject{
dynamic var min_images : Int = 0
dynamic var moreTextSize : CGFloat = 0.0
func myMethod(){
self.setValue(value, forKey: "\(min_images)")
}
}