web-dev-qa-db-ja.com

SwiftでKey-Value Observation(KVO)は利用できますか?

もしそうなら、Objective-CでKey-Valueオブザベーションを使用したときに他には存在しなかった重要な違いはありますか?

156
jonsibley

はいといいえ。 KVOは、NSObjectサブクラスに対して常に同様に機能します。 NSObjectをサブクラス化しないクラスでは機能しません。 Swiftには(少なくとも)独自のネイティブの観測システムはありません。

(他のプロパティをObjCとして公開してKVOがそれらを処理する方法については、コメントを参照してください。)

完全な例については、 Apple Documentation を参照してください。

102
Catfish_Man

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監視を実装できます。

  1. 監視したいプロパティにdynamic修飾子を追加します。 dynamicの詳細については、 動的ディスパッチの要求 を参照してください。

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
    
  2. グローバルコンテキスト変数を作成します。

    private var myContext = 0
    
  3. 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は他のクラスの動的プロパティへの変更を監視するために登録するように設計されています。

139
Rob

はいといいえの両方:

  • はい、Swiftでも同じ古いKVO APIを使用してObjective-Cオブジェクトを監視できます。
    dynamicから継承したSwiftオブジェクトのNSObjectプロパティも見ることができます。
    しかし、...いいえSwiftのネイティブ観測システムであると期待できるので、厳密には入力されません。
    CocoaとObjective-CでSwiftを使用する|キー値観察

  • いいえ、現在、任意のSwiftオブジェクト用の組み込みの値観測システムはありません。

  • はい、組み込み型のプロパティオブザーバがあり、これらは強く型付けされています。
    しかし... ...いいえこれらはKVOではありません。それらはオブジェクト自身のプロパティを観察するためだけに使用されるため、ネストされた観察をサポートしません( "キーパス")、そしてあなたはそれらを明示的に実装しなければなりません。
    The Swift Programming Language |プロパティオブザーバ

  • はい、明示的な値の監視を実装することができます。これは強く型付けされ、他のオブジェクトから複数のハンドラを追加することを可能にします。パス "#:。
    しかし、...いいえこれは、観察可能として実装したプロパティに対してのみ機能するため、KVOにはなりません。
    このような価値観を実装するためのライブラリーは、こちらから入手できます。
    Observable-Swift - SwiftのKVO - 値の観測とイベント

91
slazyk

例はここで少し助けるかもしれません。属性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)")
}
10
Paul Robinson

はい。

KVOは動的ディスパッチを必要とするので、メソッド、プロパティ、サブスクリプト、またはイニシャライザにdynamic修飾子を追加するだけです。

dynamic var foo = 0

dynamic修飾子は、宣言への参照がobjc_msgSendを通じて動的にディスパッチされ、アクセスされるようにします。

8
Bryan Luby

ロブの答えに加えて。そのクラスは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
        }
    }
}
5
onmyway133

現在Swiftは 'self'以外のオブジェクトのプロパティ変更を監視するための組み込みメカニズムをサポートしていませんので、KVOはサポートしていません。

しかし、KVOはObjective-CとCocoaの非常に基本的な部分なので、将来追加される可能性が非常に高いようです。現在のドキュメントはこれを暗示しているようです。

Key-Value監視

今後の情報.

CocoaとObjective-CでSwiftを使う

5
ColinE

1つ重要なことは、Xcode7 betaに更新すると、次のようなメッセージが表示されることです。"メソッドはスーパークラスのメソッドをオーバーライドしません。 "。それは議論の選択性のためです。観測ハンドラが次のようになっていることを確認してください。

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

これは少数の人々にとって有用であると証明されるかもしれません -

// 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を使用しました。あなたはこのコードを少し変更するだけで使用できます。

3
Nikhil Manapure

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

}
1
DrPatience