2013/12/04

ReactiveCocoaのシグナルとサブスクライブについて

前回の記事「ReactiveCocoaでmap, filterなどを使ってみる」ではmapやfilterを利用してみました。

今回は、前回の処理を別の方法で書くことを通して、ReactiveCocoaの中心的な役割を果たしているRACSignalについて説明します。

コードの書き変え

まず、前回の処理を抜き出してみましょう。

RAC(self, upperLabel.text) = [[self.textField.rac_textSignal
    filter:^BOOL(NSString* value) {
        return value.length >= 3;
    }] map:^id(NSString* value) {
        return value.uppercaseString;
    }];

それと同じことを行う別のコードはこちらになります。上のコードよりわかりやすいかもしれませんね。

// 注意: このコードはメモリリークする
[self.textField.rac_textSignal subscribeNext:^(NSString* x) {
    if (x.length >= 3)
        self.upperLabel.text = x.uppercaseString;
}];

rac_textSignalはクラスRACSignalのインスタンスを返します。 これは文字列変更ごとにその値の付いたシグナルを送信しているので、それをsubscribeしてblockで処理しているわけです。

ちなみに、前回利用したrac_textSignalのコメントにはメソッドの説明として次のように書かれています。

このテキストフィールドのテキストのためのシグナルを作成して返す。 これは常に現在のテキストから開始する。 テキストフィールドでUIControlEventEditingChangedイベントが発火したときに、 このシグナルはnextイベントを送信する。

シグナルのイベントの種類

RACSignalが送信するイベントには次の3種類があります。

  • next
  • completed
  • error

また、それらには次のようなルールがあります。

  • nextイベントは複数回送信されることがあります (0回のこともあります)。上のrac_textSignalのように値の変更ごとに何回も送信されたりします。
  • 最後にcompletedイベントかerrorイベントのどちらかが送信されることがあります (両方が送信されることはない)。これらのイベントはシグナルの終了を意味しているので、その後はどのイベント送信もできなくなります。
  • nextイベントはid型、errorイベントはNSError型の値を1つ持っています。
  • completedイベントは値を持ちません。

シグナルによるイベントの送信の順番は、「0回以上のnext」 → 「0回か1回の、completedまたはerror」という感じになります。

詳細はReactiveCocoaのFrameworkOverviewを参照してください。

これら3種類のイベントをちゃんと受けるには、次のようにRACSignalsubscribeNext:error:completed:を使います。

// 注意: このコードはメモリリークする
[self.textField.rac_textSignal subscribeNext:^(NSString* x) {
    if (x.length >= 3)
        self.upperLabel.text = x.uppercaseString;
    } error:^(NSError *error) {
        NSLog(@"Error: %@", error);
    } completed:^{
        NSLog(@"OK!");
    }];

ですが、この場合にはerrorは送信されない、かつcompletedを受けても意味がないので、subscribeNext:を利用しています。

NSDataを使った非同期処理の例

ReactiveCocoaを使うと、NSDataを使った非同期処理も次のように簡単に書けます。

#import <NSData+RACSupport.h>

NSURL* url = [NSURL URLWithString:@"http://www.google.com/"];
[[NSData rac_readContentsOfURL:url options:0 scheduler:[RACScheduler currentScheduler]]
    subscribeNext:^(NSData* x) {
        NSLog(@"Next: %s", [x bytes]);
    } error:^(NSError *error) {
        NSLog(@"Error: %@", error);
    } completed:^{
        NSLog(@"Completed!");
    }];

実行してみると、データが入ったnextイベントが送信されてからcompletedイベントが送信されます。

2013-12-04 01:26:02.928 RacSample[30881:70b] Next: <!doctype html> ... </body></html>
2013-12-04 01:26:02.939 RacSample[30881:70b] Completed!

また、存在しないURLを指定したりすると、次のようにerrorイベントだけ来ることがわかります。

2013-12-04 00:20:12.903 RacSample[10553:70b] Error: Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed. (Cocoa error 256.)" UserInfo=0x8f63c20 {NSURL=http://localhost/}

メモリリークについて

上のコードのいくつかは、コメントにあるようにメモリリークします。 これは別にReactiveCocoa特有の問題ではなく、ARCによるstrong reference cyclesの問題です。

これらの詳細と解決法については次回以降に説明します。

おわりに

ReactiveCocoaのRACSignalとイベントの種類を紹介しました。

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。