ぴよログ

↓に移転したのでこっちは更新されません、多分。

Objective-Cでの自前のイベントっぽい仕組み

移転しました →

非同期でネットワークからデータを取ってくる系の処理が終わったときにUITableViewreloadDataを呼びたいみたいなとき。どうやってやるのがいいんでしょうか?

  • delegate
  • observerパターン
  • KVO
  • ReactiveCocoa
  • 自前でなんか作ったやつ

iOSでよくあるのはdelegateなんでしょうが、非同期処理をするクラスとUITableViewを持ってるUIViewControllerが結構遠いのでdelegateにすると引きずり回すことになってあまりよくない気がしました。この方法って片方がもう片方を直接参照できるときじゃないと使いにくいですよね?

KVOとかReactiveCocoaもそんな感じがしました。

自前で作ったやつ

なんかもやもやしたのでとりあえず自分で作ってみました。これ全然ダメじゃんみたいなことはすごくありそうなのでそういうときは誰か突っ込んでくれると嬉しい。

実装

EventManagerみたいな登録と呼出の口がついているシングルトンを用意しました。

@interface EventManager : NSObject

+ (id)sharedInstance;

- (void)add:(NSString *)key target:(id)target selector:(SEL)sel;
- (void)call:(NSString *)key args:(NSArray *)args;

@end

実装はこうなっています。

- (void)add:(NSString *)key target:(id)target selector:(SEL)sel 
    [self.targets setObject:target forKey:key];
    [self.selectors setObject:NSStringFromSelector(sel) forKey:key];
}

- (void)call:(NSString *)key args:(NSArray *)args {
    id target = [self.targets valueForKey:key];
    SEL selector = NSSelectorFromString([self.selectors valueForKey:key]);
   
    [target performSelector:selector withObject:args];
}☄

エラー処理とかそういうのは勘弁してください。

使い方

冒頭に述べたようなケース、非同期処理が終わったら遠いところにいるUITableViewの再描画ってやつの例です。

まず登録のほう。

- (void)viewDidLoad
{
    [[EventManager sharedInstance] add:@"reload" target:self selector:@selector(reloadTable)];
}

- (void)reloadTable {
    [self.tableView reloadData];
}

続いて呼び出し。

- (void)hogeDidFinish:(id)sender {
    [[EventManager sharedInstance] call:@"reload" args:nil ];
}☄

お互いが直接知らなくてもEventManagerと文字列を介してなんとかreloadDataにまで辿り着くことができました。

Mediatorパターン?

EventManagerは直接知らない人同士のやりとりを仲介するためのクラスでした。ってか、これってMediatorパターンになるんですかね。

書いていて気がついたのですがdelegateを使う場合だと仲介者がいれば参照できなくても書けそうですね。今回の場合は文字列を使ったのでよりなんでも有りな書き方ができてしまいそうですが、delegateを使うとなるとプロトコルをいくつも用意することになってイマイチかも。

Notification Centerに気づく

↑まで書いたあとでNSNotificationCenterの存在を知る。いやいや、その名前じゃiPhoneで見るあの通知画面のことかと思っていたよ。ということで使い方を見てみたらさっきのEventMangerとほぼ一緒でした。

それなら標準のほうがロバストだろうし自作することはありませんね。さようならEventManager。。。