iPhone/iPadネットワークプログラミング(5)Bonjourを使ってデータを送る (3) MacからiPhone/iPadヘ送信

前回は、サービス検索側(iPhone/iPad)からサービス発行側(Mac)へ文字列を送った。
今回は、この逆で、サービス発行側から送ってみよう。つまりMac=>iPhone/iPadだ。


前回まで、iPhone/iPadからMacへ文字列が送られたとき、Mac側では NotificationCenterに登録したデータ読み込みメソッド readData: が呼ばれるようにしていた。
今回はここに、データ送信部分を追加して、iPhone/iPadからデータがきたら、こちらも送る、という仕様で実装してみる。
readData: は次のようになる。

- (void) readData:(NSNotification*)n
{
    //データ受け取り
    NSData* rData = [readHandle availableData];
    NSString* str = [[NSString alloc] initWithData:rData encoding:NSUTF8StringEncoding];
    NSLog(@"%@", str);

    //ここからデータ送信
    NSData* sendData = [[NSString stringWithFormat:@"『%@』がとどいたよん", str] dataUsingEncoding:NSUTF8StringEncoding];
    [readHandle writeData:sendData];
    [readHandle waitForDataInBackgroundAndNotify];
}

データ受け取りように作成したファイルハンドルへは書き込みもできるので、それを利用してデータを送りつける。
具体的には writeData:(NSData*)data メソッドを使えば良い。
最後に、データ受信監視をバックグラウンドで行うように設定して終了。


次に、iPhone/iPad側で受け取れるようにしないといけない。これが結構面倒。
NSInputStream と NSOutputStream には、先ほどのように便利な Notification がない。そこで、定期的に監視をするようRunLoopへ登録しなければならない。これをIP解決成功時に行う。つまり netServiceDidResolveAddress にそれを実装する。

NSInputStream* iStream;   //<-- クラス変数にしておく

- (void) netServiceDidResolveAddress:(NSNetService*)netService
{
    NSOutputStream* oStream = nil;
    iStream = nil;
    [netService getInputStream:&iStream outputStream:&oStream];  //IOストリームの取得
    //データ受けとるための準備
    if(iStream){
        [iStream retain];
        [iStream setDelegate:self];
        [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [iStream open];
    }
    //データ送信
    if(oStream){
        NSString *sendMessage = @"iPhone/iPadからMacへ文字列を送信しま〜す。";
        NSData *data = [sendMessage dataUsingEncoding: NSUTF8StringEncoding];
        [oStream open];
        [oStream write:[data bytes] maxLength:[data length]];
        [oStream close];
    }
}

送信と受信用のストリームハンドルを取得する。受信用は他の場所で使用するのでクラス変数にしておき、retainしておく。
データを受信したときにどのクラスが処理を担当するかを設定(デリゲートの設定)し、RunLoopに登録する。
最後に、ストリームをオープンしてから、データを送信する。


runLoopにNSInputStream を登録すると stream: handleEvent: が定期的に呼ばれる。ここでデータの受け取りを行う。

- (void) stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
    uint8_t buf[100];
    switch(eventCode){
        case NSStreamEventHasBytesAvailable:  //読み込めるデータがある場合
            memset(buf, 0, sizeof(buf));
            //サイズ指定でしか読めないので、配列に収まらない場合はもう一回読むか、配列を大きめに取る。
            [iStream read:buf maxLength:100];
            NSData* data = [NSData dataWithBytes:buf length:100]; //NSData型で送っているので、NSDataに戻す
            NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; //文字列へ
            NSLog(@"%@", str);
            break;
        }
    }

eventCode に stream の状態が入ってくる。ストリームに読み込み可能なデータがある場合、NSStreamEventHasBytesAvailable が入っている。つまり、NSStreamEventHasBytesAvailable の場合に読み込むように実装すればよい。読み込みには read:(uint8_t*) maxLength:(int) を使う。長さを指定しなければいけないので、少し面倒だ。あらかじめ配列も用意しないといけないしね。一方で、NSFileHandleでは必要ないので(いきなりNSDataでとれるので)らくちんだ。Mac側はそれで実装してたよね。
実は、双方NSFileHandleで実装できる。最初の準備がややこしくなるが。。。まぁ、最初に苦労するか、読み取りに苦労するか、であるけど。まぁ、苦労って言ってもたいしたことではないが。。。個人的には読み込みは楽したいねぇ。この辺は後日に。


Mac <=> iPhone/iPad ができるようになった。が、いろいろ問題があるんだよね。iStrem release してないとか。相変わらず、単発だし。まぁ、単発なのは oStream をクラススコープにすれば解決できるけど。とまぁ、いくつかあるので、次は、この辺を対処するには・・・というところをやってみたい。