読者です 読者をやめる 読者になる 読者になる

KVO(key value observing, キー値監視)について

Objective-c

今回は便利なKVOについて紹介します。
KVOはKVC(key value coding)とペアになって使われるもので、特定のオブジェクトのプロパティが変化したことを
監視する場合などに使います。

私も言葉は聞いたことがありましたが使う機会がありませんでした。今回実際に使用す
る機会があり、とても便利だと感じたので紹介したいと思います。

使用したのは次のような状況です。
AVFundationを使用してカメラアプリを作成していたのですが、デフォルトのカメラアプリ
と同じようにピント合わせと同時に枠線を表示する必要が出て来ました。
UIImagePickerControllerを使えば枠線が自動で出るのですが、AVFundationを使っているので自分で
枠線を表示し、さらにピントがあったタイミングで枠線を非表示するにする必要があります。
ピントの情報は、AVCaptureDeviceクラスのプロパティであるadjustingFocusのBOOL値を調べる事で分かります。

そしてプロパティが変化したタイミングを知るにはKVOを使って以下のように実装すれば
良い事が分かりました。
※但しここではKVOの使い方を説明しやすくするために、UIImagePickerControllerを
使った例を示します。

KVOを利用するために、NSKeyValueObserving.hの以下のメソッドを用います。

@interface NSObject(NSKeyValueObserving)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end

@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@end
ViewController.m
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
@property (nonatomic) UIImagePickerController *imagePicker;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.imagePicker = [[UIImagePickerController alloc] init];
    self.imagePicker.delegate = self;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    AVCaptureDevice *camDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    // ここでAVCaptureDeviceを登録します。selfを指定する事でこのクラス内で通知を受け取る事が出来ます。
    [camDevice addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    // 登録が不要になったら解除します。
    AVCaptureDevice *camDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    [camDevice removeObserver:self forKeyPath:@"adjustingFocus"];
}

// StoryBoardにてUIButtonと接続しておきます。
- (IBAction)cameraButtonPushed:(id)sender {
    if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
    {
        self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
        [self presentViewController:self.imagePicker animated:YES completion:nil];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if( [keyPath isEqualToString:@"adjustingFocus"] ){
        // ピントの状態が変化する度にこのコードが呼ばれます。
        // ここに実装を書く事でピント合った/外れたタイミングで枠の表示/非表示が出来ます。
    }
}
@end

KVOの詳しい説明については、Key-Value Observing Programming Guide: Introduction to Key-Value Observing Programming Guide
を参考にしてもらうとして、このようにKVOという仕組みを使うことでとても簡単に特定のクラスの状態を監視する事が出来ます。
既存のフレームワークのような手を出せないものに対しても簡単に通知を受け取るれるこの仕組はとても便利ですね!
※正確にはKVOを扱うための条件が存在するので、ドキュメントを読むなどして確認して下さい。大抵は使えます。