|ハイブリッドOS|File System|ARM|Android|Java|制御システム|オープンシステム

 

技術者コラム

 
フォーム
 
第9回目:iOSの非同期API(Part4)
2014-04-06
筆者:村田
 
こんにちは。
 
前回、ブロック構文を用いたプロトコルデリゲートの置き換えを紹介したのですが、使いどころと注意点についてコメントしたいと思います。
 
[使いどころ]
①例としてあげたHTTP通信開始→終了イベント通知のように非同期処理の開始とイベント通知にシーケンシャルな関連性が強い場合には、処理を近くに書けるので可読性向上のメリットがあります。
 
// 処理開始
AsyncMethodStart: request
didEventA:^() {
  // 何かイベント処理
}
didEventB:^() {
  // 何かイベント処理
}
 
②ブロック構文はクロージャなので、呼び出し側の自動変数を非同期処理側に簡単に渡せます。プロトコルデリゲートの場合、インスタンス変数を経由しなければならないので少々面倒です。
 
[注意点]
 
①View(画面)のイベントハンドラはシーケンシャルな関連がないので、わざわざブロックで書けるようにする必要は無いと思います。素直にプロトコルを実装した方がいいでしょう。
 
②ブロックの中でブロックを書くような以下の書き方は、JavaScriptなどで良く見られますが、インデントが深くなりメソッドも大きくなるのであまりオススメできません。
 
AsyncMethodA: request
didEventA:^() {
  AsyncMethodB: request
  didEventB:^() {
  }
}
 
最後に突然横道に逸れますが、C#の構文を一つ紹介したいと思います。まず注意点②の極端な例を見てみましょう。(以下はObjC風疑似コード)
 
-(void) hoge
{
  AsyncConnectMethod:                   // 非同期接続を開始して、
  didConnectEvent:^() {                   // 接続完了したら、
    AsyncReceiveMethod                   // まずは受信を開始して、
    didReceiveEvent:^() {                  // 受信できたら、
      AsyncSendMethod:                    // 応答を送信して、
      didSendCompleteEvent:^() {     // 送信完了したら・・・
      }
    }    
  }
}
 
上記のようにブロックの中で次の非同期処理を連鎖をさせる場合、インデントが深くなり読みづらいコードになります。C#では非同期メソッドの待ち合わせを行うawaitという演算子があり、以下のように書けます。
 
async void hoge()
{
  await AsyncConnectMethod();
  await AsyncReceiveMethod();
  await AsyncSendMethod();
}
 
AsyncConnectMethodを呼び出した後、非同期処理が完了するまで一度hogeを抜けて別の処理を実行でき、非同期処理が終わるとhogeの続きのAsyncReceiveMethodから処理を復帰継続します。シーケンシャルなクロージャ連鎖には持ってこいの記法です。
 
別の言語ではクロージャの連鎖を「継続」って呼んだり、「コルーチン」をクロージャ連鎖で実現していたりと、クロージャ関連は面白いネタになりそうです。きちんと勉強したら記事にまとめたいと思います^^
 
以上
 
第8回目:iOSの非同期API(Part3)
2014-03-30
筆者:村田
 
こんにちは。
 
今回は、基本的な実装方法であるプロトコルによるデリゲートパターンを、前回紹介したブロック構文を用いて置き換えてみたいと思います。
 
・ポイント1: 置き換え対象のクラスを継承します。(継承ではなくコンポジションでも可能です)
・ポイント2: 置き換え対象のコールバックメソッドのシグネチャから、typedefでブロック型を定義します
・ポイント3: 置き換え対象のコールバックメソッド内でブロックに呼びかえます。(ブロックはコンストラクタ等で貰って保持しておきます)
 
今回はNSURLConnectionを継承し、データ受信完了時のメソッド①とエラー発生時のメソッド②をブロックに置き換えましょう。プロトコルで定義されたメソッドを全て置き換える必要はなく、対象のメソッドは自由に選べます。
① (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
② (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
 
早速コードを見てみましょう。
 
[ヘッダ] ExNSURLConnection.h (ヘッダの名前はNSURLConnectionの拡張なのでExと付けてみました。何でもいいです。)
 
// ①②のコールバックメソッドからブロック型を定義します。型名はdidiReceiveDataとdidOccuerErrorにしてみました
// OnReceiveDataやOnOccurErrorとかでも良さそうです
typedef void (^didReceiveData)(NSData *data);
typedef void (^didOccurError)(NSError *error);
 
@interface ExNSURLConnection : NSURLConnection
{
    didReceiveData didReceiveData_; // ブロックを保持するインスタンス変数です
    didOccurError didOccurError_;   // 変数名は工夫してもいいですが、機械的に「型名_」にしました
}
 
// コンストラクタです。ここでブロック変数を利用者から渡して貰いましょう。(コンストラクタ以外のメソッドから貰っても良いです)
+ (id)startWithRequest:(NSURLRequest *)request 
                       didReceiveData:(didReceiveData)didReceiveData 
                       didOccurError:(didOccurError)didOccurError;
@end
 
 
[実装] ExNSURLConnection.m
 
@implementation ExNSURLConnection
 
// 利用者に提供するコンストラクタです。その下のinitWithRequestを呼び出すだけのstaticメソッドです
+ (id)startWithRequest:(NSURLRequest *)request 
                       didReceiveData:(didReceiveData)didReceiveData 
                       didOccurError:(didOccurError)didOccurError
{
    return [[SOFURLConnection alloc] initWithRequest:request
                                     didReceiveData:didReceiveData didOccurError:didOccurError];
}
 
// コンストラクタ本体です
- (id)initWithRequest:(NSURLRequest *)request 
                      didReceiveData:(didReceiveData)didReceiveData
                      didOccurError:(didOccurError)didOccurError
{
    // まず本来のNSURLConnectionのコンストラクタを呼び出します。(super)
    // delegate引数には自身(self)を指定します。
    // 利用者のブロックオブジェクトをcopyしてインスタンス変数に保持します。
    if (self = [super initWithRequest:request delegate:self startImmediately:NO]) {
        didReceiveData_ = [didReceiveData copy];
        didOccurError_ = [didOccurError copy];
    }
    return self;
}
 
// ①のコールバックメソッドが呼ばれたら、ブロック呼び出しに置き換えます
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    didReceiveData_(data);
}
 
// ②のコールバックメソッドが呼ばれたら、ブロック呼び出しに置き換えます
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    didOccurError_(error);
}
 
@end
 
どうでしょう、プロトコルデリゲートをブロック呼び出しに置き換えるという意図が伝われば良いのですが・・・
 
続いて、クラス利用者側からどう変わるのか見てみましょう
 
- (void)hoge
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@“http://www.example.com/index.html”]];
 
    // 非同期HTTP受信オブジェクト生成
    ExNSURLConnection *connection = [ExNSURLConnection startWithRequest:request
    didReceiveData:^(NSData *data) {
        // データを受信したら・・・
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
       
    } didOccurError:^(NSError *error) {
        // エラーが発生したら・・・
        NSLog(@"connection error”);
    }];
 
    // 受信開始
    [connection start];
}
 
クラス利用者はプロトコルを実装する代わりに、startWithRequestの引数にコードブロックを書くだけに変わりました。
Objective-Cではメソッド引数名の前にメッセージキーワードというシンボルが付きますので、
didReceiveData:^(引数) {
  処理;
} didOccurError:^(引数) {
  処理;
}
こんな感じでメッセージキーワードを適切に考えておけば、どのようなイベント時にコードブロックが実行されるかが分かりやすくなる効果があります。
 
以上
 
 
第7回目:iOSの非同期API(Part2)
2014-03-19
筆者:村田
 
こんにちは。
 
前回は非同期APIのサンプルコードとして、NSURLConnectionの例を紹介しました。プロトコルを用いたコールバックのパターンは標準的ですが、時々書きづらいことがあります。ちょっとHTTP通信を利用したいだけなのに、ヘッダに「これからプロトコルを実装する!」と明示する点が煩わしいです。それにコールバックの処理がたった1行だとしてもメソッドをオーバーライドしてその中で記述しなければなりません。また、非同期APIのメソッド呼び出しと、そのコールバックの記述が分離してしまうので処理の流れが見えづらくなります。シーケンスを意識したコールバックを扱う場合ではなるべく近くに集めて書けた方が良いでしょう。
 
このような場合、プロトコルを実装する代わりに無名関数リテラルでコールバックを書けると効果的です。他言語の例ではRubyのブロック、C#のラムダ式などを使いたくなる状況です。
 
iOS開発で使用するclangコンパイラはクロージャを作れるブロック構文が使えます。C言語の拡張機能らしいです。他のCコンパイラでも使えるといいのですが、どうなんでしょう。
 
それではブロック構文を紹介します。まずはブロックオブジェクトの生成式を見てみましょう。
^(戻り値型) (引数) { 処理; }; という風に書きます。具体例として数値を表示するブロックを変数hogeに入れてみます。
 
hoge = ^void (int a) { NSLog(@"%d", a); };
 
変数にいれたコードブロックは以下のようにして簡単に呼び出せます。
 
hoge(10);  // NSLogを使って10を表示
 
引数や戻り値の型は省略できるので、以下みたいなのもアリです。
 
hoge = ^{ NSLog(@"Hello"); };
hoge(); // Helloと表示する
 
型の定義をもう少し見てみましょう。C言語の関数ポインタに似たシンタックスです。コード例を見た方が早いでしょう。
 
int (^hoge)(int); // intを引数にintを返す、ブロック型の変数hogeを宣言
hoge = ^int (int a){ return a * 2; }; // ブロックを生成してhogeに代入
int piyo = hoge(10); // 10を引数に渡してhogeを呼ぶ (=>piyoは20)
 
ブロックの型宣言は読みにくいのでtypedefします。Cの関数ポインタでも常套句ですね。
 
typedef void (^BlockTypeName)(int); // BlockTypeNameが新しい型名
BlockTypeName block = ^(int a) { return a * 2; }; // 型の宣言が見やすくなりました
 
前半に述べたプロトコルの煩わしさを少し(?)改善するため、次回はプロトコルとブロック構文を連携させてみましょう。
 
第6回目:iOSの非同期API(Part1)
2014-03-08
筆者:村田
 
こんにちは。
 
前回のHaskellの続きではなく、何回かに分けてiOSの非同期APIをテーマにしたいと思います。
よろしくお付き合い下さい。(Haskellの続きはその後で)
 
以前社内輪講でも取り上げた非同期APIですが、iOSだけでなくWindows、Node.jsなど様々なプラットフォームでその重要性は増し、従来は同期APIしか提供されていなかった処理も非同期APIとして利用できるようになっています。
 
同じ処理を行う同期APIと非同期APIがあった場合、同期APIを選択しつつスレッドによる非同期化を行うこともできますが、実装の難しさ、可読性の低さから今後はバッドノウハウとなるかもしれません。
 
さて、iOSのプログラミングでもこの非同期APIがあちこちに出てきます。例えば以下のようなAPIです。
 
[内容] URLのリソースにアクセスしてデータを取ってくる
[使用するクラス] NSURLConnection
[使用する非同期メソッド]
- (id)initWithRequest:(NSURLRequest *)request
   delegate:(id < NSURLConnectionDelegate >)delegate
 
2番目の引数delegateにNSURLConnectionDataDelegateプロトコルを実装したオブジェクトを渡します。プロトコルとはJava/C#で言うところのinterfaceのようなものと考えて下さい。(少し違いますけど)
NSURLConnectionDataDelegateプロトコルには例えば以下のようなメソッドが定義されています。
 
[NSURLConnectionDataDelegateのメソッド]
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
 
didReceiveData(※)という名前から想像してみましょう。HTTPで受信したデータを引数dataにセットしてコールバックしてくれるメソッドです。これを利用して非同期HTTP受信を実現します。
 
※横道にそれますが・・
didReceiveDataという名前は何でしょう。引数名ではありません。引数名はdataです。didReceiveDataという名前はメッセージキーワードと言い、メソッド名の一部なのです。上記メソッドの正しい名前はconnection:didReceiveData:という風にメッセージキーワードを繋げたものになります。
 
もう少しソースを見てみましょう。前回同様、Objective-Cに不慣れな方も短いプログラムですので眺めてみて下さい。
 
[ヘッダ] AsyncHttpTest.h
 
@interface AsyncHttpGet : NSObject <NSURLConnectionDataDelegate>
- (void)start;
@end
 
ヘッダを見ましょう。何となくクラス定義っぽく見えますか?@interface〜@endは他言語で言えばpublic class { } です。NSObjectが基底クラスです。Java/C#で言うところのObjectです。<>内にプロトコル名を書いて実装することを明示します(※)。
 
※また横道にそれますが・・
本ページではプロトコルの「実装(Implement)」と言っていますが、Objective-Cでは「採用(Adopt)」って言うのが正しいです。Javaのimplements宣言はメソッド実装を強制するものですが、Objective-Cはもっとゆるいニュアンスです。
 
[実装] AsyncHttpTest.m
 
@implementation AsyncHttpGet
- (void)start
{
    NSString *urlstr = @"http://www.yahoo.co.jp"; // 文字列から
    NSURL *url = [NSURL URLWithString:urlstr];       // URLを生成して
    NSURLRequest *request = [NSURLRequest requestWithURL:url]; // HTTPリクエストを生成
    NSURLConnection *connection =
      [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
 
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSString *str =
      [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", str);
}
@end
 
そして実装です。まずはconnection:didReceiveData:という2つ目のメソッドを見てみましょう。これは、NSURLConnectionDataDelegateプロトコルで定義されているメソッドをオーバライドしたものです。前述のとおりHTTP受信後、OSからコールバックされます。受信した文字列をコンソールに表示しています。
 
startメソッドは、yahooに接続するためのRequestを作りinitWithDataメソッドで受信を開始しています。非同期APIなので受信完了を待たずに戻ってきます。引数delegateにselfを渡しており、受信完了後にコールバックされるオブジェクトに自分自身を指定しています。(selfは他言語でいうところのthisです)
 
このパターンはたくさんあります。メソッドのマニュアルを見てdelegateという引数があれば大体このパターンです。そのクラスに関連するコールバック集を定義した<ほにゃほにゃDelegate>プロトコルがありますので、それを実装したオブジェクトを渡すというパターンです。
 
次回はこのパターンについてもう少し考えてみたいと思います。
 
第5回目:Haskellで懐かしの3の倍数
2014-01-23

筆者:村田

 

こんにちは。

 

突然ですが、最近関数型プログラミングってよく耳にしませんか?

雑誌の特集テーマになっていたり、Haskell/Scalaといった

関数型言語の本を書店で見かけたり。

先日、「JavaScriptで学ぶ関数型プログラミング」なんて本も出版され

キーワードとして少しずつ身近な存在になってきていると思います。

またJava8では、無名クラスで書いていたような一部の処理を対象に

ラムダ式をサポートし、フィルタの考え方に基づいたコレクションAPIが追加され

関数型プログラミングのエッセンスが加わるようです。

 

そこで、今すぐ業務で使うことは無いでしょうが、話のネタにでもという感じで

関数型プログラミング(主にHaskell)に触れてみたいと思います。

 

では早速プログラムです。

 

-- worldnabe.hs

worldnabe :: [Int] -> [String]

worldnabe [] = []

worldnabe (x:xs)

 | x `mod` 3 == 0 = ["Aho"] ++ worldnabe xs

 | otherwise           = [show x] ++ worldnabe xs

 

3の倍数のとき「エイホ」と表示するHaskellプログラムです。

GHCiというHaskellインタプリタで実行した結果は以下の通り。

 

*Main> worldnabe [1..20]

["1","2","Aho","4","5","Aho","7","8","Aho","10","11","Aho","13","14","Aho","16","17","Aho","19","20"]

 

Haskell初見の方も短いプログラムですので眺めてみてください。

細かい文法を知らなくても分かる部分がありそうです。

・--で始まる1行目は?

・3の倍数の検査はどこ?

・C言語で書くとしたらループ・分岐を使うと思いますが、

  このプログラムでは大体どのあたりで表現されているのか?

 

えー、本プログラムの説明ついては次回以降のネタにまわします・・

 

なおこのプログラムは不十分です。

3の倍数だけでなく3のつく数字(13)も「エイホ」にすべきです:P

(isInfixOf関数が使えそうです)

 

連載記事のソースコード

連載記事のソースコード
 
・Haskellで問題を解く(Part1〜Part4) ソースコード
・Clojureで8クイーン問題にチャレンジ(Part1〜Part5) ソースコード
・OCamlでへびゲームを作る(Part1〜Part5) ソースコード
・Swiftでオセロを作る(Part1〜Part5) ソースコード
・Processingでシューティング(Part1〜Part4) ソースコード 
Haskellでテトリス(Part1〜Part9) ソースコード
・プチコン3号(BASIC)でさめがめ(Part1〜Part3) ソースコード
・Prologでさめがめを解く(Part1〜Part6) ソースコード