[Objective-C]KVC - Key Value Observingをいい感じにする

こんにちは!

エンジニアのTです。

最近はMVCやMVVMについて勉強しています。少しは理解できた気がしますが、解釈の仕方や実装の方法も、人それぞれだったりするので、ベストな方法を模索中です。

さて、今回はそれらでよく使われるKVOです。

モデルのプロパティが更新された時の通知に用いられる方法の一つで、1対多の通知を行う事ができるのでとても便利です。

ただ、observeValueForKeyPathで全てプロパティ更新を受け止めていると、条件分岐の嵐になってクールじゃないです。

なので、何か良い方法はないか調べていた所、とても便利な方法を見つけたので紹介します。

KVODispatcher.h
@interface KVODispatcher : NSObject

- (id)initWithOwner:(id)theOwner;
- (void)startObserving:(id)object keyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options;
- (void)startObserving:(id)object options:(NSKeyValueObservingOptions)options keyPaths:(NSString*)keyPaths,... NS_REQUIRES_NIL_TERMINATION;

@end

KVODispatcher.m
#import "KVODispatcher.h"
#import <objc/runtime.h>

@interface KVODispatcher()

/// 監視対象のオブジェクト
@property (nonatomic, strong) NSMutableArray* monitoredObjects;
/// 監視するオブジェクト
@property (nonatomic, weak) id owner;

@end

@implementation KVODispatcher

- (id)initWithOwner:(id)theOwner
{
    self = [super init];
    
    if (self) {
        self.owner = theOwner;
        self.monitoredObjects = [NSMutableArray array];
    }
    
    return self;
}

- (void)dealloc
{
    [self removeAllObserver];
}

/**
 プロパティ値の監視を始めます。
 
 @param object  監視対象のオブジェクト
 @param keyPath 監視するプロパティ名
 @param options オプション
 */
- (void)startObserving:(id)object keyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options
{
    NSString *capitalisedSentence = [keyPath
        stringByReplacingCharactersInRange:NSMakeRange(0,1)
                                withString:[[keyPath  substringToIndex:1] capitalizedString]
    ];
    
    SEL selector = NSSelectorFromString([NSString stringWithFormat:@"on%@Changed:keyPath:change:", capitalisedSentence]);
    [object addObserver:self forKeyPath:keyPath options:options context:selector];
    
    NSDictionary* dic = @{
        @"object" : object,
        @"keyPath" : keyPath
    };
    
    if ([self.monitoredObjects indexOfObject:dic] == NSNotFound) {
        [self.monitoredObjects addObject:dic];
    }
}

/**
 配列で指定されたプロパティ郡を一括登録し、監視を始めます。
 
 @param object  監視対象のオブジェクト
 @param options オプション
 @param keyPaths 監視するプロパティ名
 */
- (void)startObserving:(id)object options:(NSKeyValueObservingOptions)options keyPaths:(NSString*)keyPaths,...
{
    va_list arg;
    
    va_start(arg, keyPaths);
    
    NSString* value = keyPaths;
    
    while (value) {
        [self startObserving:object keyPath:value options:options];
        
        value = va_arg(arg, typeof(NSString*));
    }
    
    va_end(arg);
}

/**
 プロパティの更新があった場合に呼ばれます。
 指定のセレクターを呼び出します。
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    objc_msgSend(self.owner, (SEL)context, object, keyPath, change);
}

/**
 全ての監視を解除します
 */
- (void)removeAllObserver
{
    for (int i = 0; i < self.monitoredObjects.count; i++) {
        NSDictionary* dic = (NSDictionary*)self.monitoredObjects[i];
        
        id object = dic[@"object"];
        NSString* keyPath = (NSString*)dic[@"keyPath"];
        [object removeObserver:self forKeyPath:keyPath];
    }
    
    [self.monitoredObjects removeAllObjects];
}

@end

使い方

KVODispatcher* kvoDispatcher = [[KVODispatcher alloc] initWithOwner:self];
[kvoDispatcher startObserving:hoge keyPath:@"hogeProperty" options:NSKeyValueObservingOptionInitial];

まずは、監視対象のオブジェクトを指定してプロパティを登録。
hogePropertyに変更があったら、下記のメソッドが呼ばれる。

- (void)onHogePropertyChanged:(id)object keyPath:(NSString*)keyPath change:(NSDictionary *)change
{
    // 更新処理
}

こんな感じでon[プロパティ名]Changed:keyPath:changeが呼ばれるようになります。
ちゃんと、プロパティ名の頭一文字を大文字にしてくれます。

一気にプロパティを登録する事もできます。

[kvoDispatcher startObserving:hoge
                          options:NSKeyValueObservingOptionInitial
                         keyPaths:@"hoge1",
                                        @"hoge2",
                                        @"hoge3",
                                        @"hoge4",
                                        @"hoge5", nil];

これをうまく使えば、モデルのしっかりとした分離が捗りそうです!

応用として、紐づけておいたViewのプロパティを自動更新するものも作れそうですね。


参考サイト:

Need some tips regarding the Cocoa MVC/KVO patterns



トンガルマンWebサイト
https://tongullman.co.jp/index.php
facebook
https://www.facebook.com/Tongullman

0 件のコメント:

コメントを投稿