Help us understand the problem. What is going on with this article?

KVO(Key-Value Obeserving)でcontextを設定する

More than 3 years have passed since last update.

KVOを使う場合に「よく分からないけれど、とりあえずcontextにはnilを渡しておけばいいいんでしょ!?」と考えて機械的にnil設定している人は実は意外に多いのではないでしょうか?実際、多くの場合、contextは使わなくても問題ありません。しかし、キー値監視するスーパークラスにおいても同じkey/valueを監視しているようなケースにおいてはcontext情報を使ってイベントを識別する必要があります。
下記は監視対象モデルクラスがPerson、「contextを設定して」キー値監視するクラスがViewControllerである場合の例です。後半部分でコード中の各注釈に関して補足説明します。

サンプルコード

環境
- Xcode 7.1.1
- Swift 2.1
- iOS 9.1

Person.swift
import Foundation

class Person: NSObject {

    private(set) dynamic var name: String
    dynamic var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
ViewController.swift(一部抜粋)
import UIKit

// (1) プライベートなグローバル変数を用意
private var kViewControllerContext: UInt8 = 0

class ViewController: UIViewController {

    @IBOutlet weak var age: UITextField!

    lazy var jonathan = Person(name: "Jonathan", age: 20)

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

         // (2) コンテキストとしてアドレスを渡す
        let opts: NSKeyValueObservingOptions = [.New, .Old]
        jonathan.addObserver(self, forKeyPath: "age", options: opts, context: &kViewControllerContext)
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        jonathan.removeObserver(self, forKeyPath: "age", context: &kViewControllerContext)
    }


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

        // (3-1) アドレスを使って自身へのイベントであるか識別する
        if context == &kViewControllerContext {
            print("self: \(self), keypath: \(keyPath), object: \(object), change: \(change), context; \(context)")

            // (4) おまけ
            let pointer = UnsafeMutablePointer<UInt8>(context)
            let value = pointer.memory
            print("pointer: \(pointer)")
            print("value: \(value)")

        // (3-2) 自身以外のイベントについてはスーパークラスへ        
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }


    @IBAction func changeValue(sender: AnyObject) {
        if let number = Int(self.age.text!) where number >= 0 {
            print("number: \(number)")
            self.jonathan.age = number
        }
    }

}

補足説明

サンプルコードにおいて特に重要なのは(1)と(3-2)です。

(1) プライベートなグローバル変数を用意
context識別にはアドレスを使います。そのアドレスが割り当てられるプライベートなグローバル変数を用意します。通常、SwiftではUIntよりもIntを使用することが推奨されていますが、今回のケースではアドレスを使うことが目的であるため、サイズの明確化及び無駄なサイズを使わないようにするためにUInt8を使っています。

(2) コンテキストとしてアドレスを渡す
(1)において用意した変数のアドレスをコンテキストとして渡します。

(3-1) アドレスを使って自身へのイベントであるか識別する
自身に対するイベントであるかを識別した上で、必要な処理を実装します。

(3-2) 自身以外のイベントについてはスーパークラスへ
自身に対するイベントでない場合は、スーパークラスに対するイベントである可能性があるため、スーパークラスのメソッドを呼び出します。

(4) おまけ
context識別にアドレスを利用するのがポイントであるため、その中身である値自体にはあまり意味がありませんが、下記のようにして値を取り出すことができます。

C-APIによる値取り出し
let pointer = UnsafeMutablePointer<UInt8>(context)
let value = pointer.memory
print("pointer: \(pointer)")
print("value: \(value)")

最後に

どんな場合においてもcontextを設定する必要があるわけではありません。設定する必要がないことが明確な場合は、contextにはnilを渡すだけで十分だと思います。

[参考URL]
Adopting Cocoa Design Patterns

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away