もしそうなら、Objective-CでKey-Valueオブザベーションを使用したときに他には存在しなかった重要な違いはありますか?
はいといいえ。 KVOは、NSObjectサブクラスに対して常に同様に機能します。 NSObjectをサブクラス化しないクラスでは機能しません。 Swiftには(少なくとも)独自のネイティブの観測システムはありません。
(他のプロパティをObjCとして公開してKVOがそれらを処理する方法については、コメントを参照してください。)
完全な例については、 Apple Documentation を参照してください。
SwiftではKVOを使用できますが、dynamic
サブクラスのNSObject
プロパティにのみ使用できます。 bar
クラスのFoo
プロパティを観察したいとします。 Swift 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
実装に渡します。また、適切な場合は、オブザーバーとしての自分を削除するようにしてください。たとえば、割り当て解除されたオブザーバを削除することができます。
Swift 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デザイン規約の採用のキー値監視セクションで説明されています。 CocoaおよびObjective-CでのSwiftの使用ガイドの の章:
キー値監視は、他のオブジェクトの指定されたプロパティへの変更をオブジェクトに通知することを可能にするメカニズムです。クラスが
NSObject
クラスから継承する限り、SwiftクラスでKey-Value監視を使用できます。これら3つのステップを使用して、SwiftでKey-Value監視を実装できます。
監視したいプロパティに
dynamic
修飾子を追加します。dynamic
の詳細については、 動的ディスパッチの要求 を参照してください。class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
グローバルコンテキスト変数を作成します。
private var myContext = 0
Key-pathにオブザーバを追加し、
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に対応したSwiftとCocoaおよびObjective-Cの使い方のガイドからは削除されましたが、それでも、この回答の冒頭で概説したように動作します。]
Swiftがそれ自身のネイティブ プロパティobserver システムを持っていることは注目に値しますが、それはそれ自身のプロパティの観察時に実行されるそれ自身のコードを指定するクラスのためのものです。一方、KVOは他のクラスの動的プロパティへの変更を監視するために登録するように設計されています。
はいといいえの両方:
はい、Swiftでも同じ古いKVO APIを使用してObjective-Cオブジェクトを監視できます。dynamic
から継承したSwiftオブジェクトのNSObject
プロパティも見ることができます。
しかし、...いいえSwiftのネイティブ観測システムであると期待できるので、厳密には入力されません。
CocoaとObjective-CでSwiftを使用する|キー値観察
いいえ、現在、任意のSwiftオブジェクト用の組み込みの値観測システムはありません。
はい、組み込み型のプロパティオブザーバがあり、これらは強く型付けされています。
しかし... ...いいえこれらはKVOではありません。それらはオブジェクト自身のプロパティを観察するためだけに使用されるため、ネストされた観察をサポートしません( "キーパス")、そしてあなたはそれらを明示的に実装しなければなりません。
The Swift Programming Language |プロパティオブザーバ
はい、明示的な値の監視を実装することができます。これは強く型付けされ、他のオブジェクトから複数のハンドラを追加することを可能にします。パス "#:。
しかし、...いいえこれは、観察可能として実装したプロパティに対してのみ機能するため、KVOにはなりません。
このような価値観を実装するためのライブラリーは、こちらから入手できます。
Observable-Swift - SwiftのKVO - 値の観測とイベント
例はここで少し助けるかもしれません。属性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
を通じて動的にディスパッチされ、アクセスされるようにします。
ロブの答えに加えて。そのクラスはNSObject
から継承しなければなりません、そして私たちはプロパティ変更を引き起こす3つの方法があります
NSKeyValueCoding
からsetValue(value: AnyObject?, forKey key: String)
を使用する
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
を使用してください。 Swift Type Compatibility を参照してください。
メソッドの実装を動的に置き換えるキー値監視などの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
}
}
}
現在Swiftは 'self'以外のオブジェクトのプロパティ変更を監視するための組み込みメカニズムをサポートしていませんので、KVOはサポートしていません。
しかし、KVOはObjective-CとCocoaの非常に基本的な部分なので、将来追加される可能性が非常に高いようです。現在のドキュメントはこれを暗示しているようです。
Key-Value監視
今後の情報.
1つ重要なことは、Xcodeを7 betaに更新すると、次のようなメッセージが表示されることです。"メソッドはスーパークラスのメソッドをオーバーライドしません。 "。それは議論の選択性のためです。観測ハンドラが次のようになっていることを確認してください。
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)")
}
}