2011」5日目担当のfaultierです。最近使ったのでNSURLProtocolネタで。NSURLProtocolって何?Foundationフレームワークで最初から扱えるプロトコルはhttp、https、ftp、fileの4つ。これ以外のプロトコルでの通信をNSURLConnectionやNSURLDownloadなどで扱う場合や、特定のリクエストに限って特別な処理をしたい場合などに、NSURLProtocolを継承して登録することで使えるようになる。ちなみに、他のアプリからopenURLしたときにアプリを起動させるカスタムURLスキームとはまた別なので注意。Systemを使うときにだけ影響するもの。最低限必要なのは、+canInitWithRequest:、+canonicalRequestForRequest:、-startLoading、-stopLoadingの4つ。こんな感じで、どんなリクエストのときにそのURLProtocolをが処理するのかを決める。isEqualToString:@"udon"];}ここでYESを返すとこのクラスがインスタンス化されて通信処理に進む。NOの場合は他に登録されているURLProtocolのこのメソッドが登録時の逆順に呼ばれて行く。この場合はudonというスキーム、例えばudon://marukame/bukkake/coolみたいなURLへのリクエストの時に処理をすることになる。別にこれは独自のスキームである必要ではなく、httpやfileなどをフックすることもできるし、特定のホストや特殊なヘッダが付いている時だけ処理するようなこともできる。次は+canonicalRequestForRequest:。リクエストをcanonicalな形に変える必要がある場合はここで弄るのだけど、特に何もすることが無ければrequestをそのまま返してやればいい。request;}その後実際の通信が始まり、-startLoadingが呼ばれる。NSURLProtocolのオブジェクトは、id<NSURLProtocolClient>のclientと、NSURLRequestのrequestというプロパティを持っているので、requestからどんなリソースが必要なのかを判断して、clientに対してデータを返してやる、という形で通信の中継をする。NSURLProtocolClientのメソッドは大体NSURLConnectionのdelegateと対応しているので、NSURLConnectionでの通信を実装したことがあれば分かるはず。NSURLConnection側でキャンセルされたときはstopLoadingが呼ばれる。簡単な例としては以下のようになる。-URLProtocol:didFailWithError:を呼ぶ。ここでキャンセルの処理を実行する。Systemに登録されていないので、適宜NSURLProtocolの+registerClass:、+unregisterClass:呼んであげることで使えるようになる。その独自スキームを使うUIViewControllerとか、あるいはもうクラスのloadメソッドで登録してしまうとか。});}使いどころそれどういうときに使うの?http以外のプロトコルとかそんな実装しないよ?と思うかもしれないけど、意外と使い道がある。UIWebView等での通信は裏側でNSURLConnectionを使っているので、WebView内での通信をフックしてアプリ側で弄ったりできるのだ。CoreDataにあるデータをJSON形式にして返すようにしておくとか、JSからのトリガーでアプリ内のバックグラウンド処理を走らせるとか、画像をファイルキャッシュしておいてオフラインでも表示するとか、複数の異なる形式のWebAPIをプロキシして同じ形で扱えるようにしたり、ということも簡単にできる。逆に「特定のリクエストに限って特別な処理をする」ではなく「特定のリクエストはスルーするけど他はブロックする」のような使い方もできる。最近(というには結構前からだけど)話題になったのは、iPhoneのWebViewにこういう脆弱性がある、という話。デバイスのアドレス帳や着信履歴みたいなのの生のデータにJSからアクセスできる必要があるのか良く分からないし、おかしな仕様だと思うのでバージョンアップで塞がれるだろうとは思うけど、少なくともiOS5.0まではこの問題は残っているので、「アプリ内でUIWebViewにloadHTMLStringさせて、外部から取得したHTMLをfile://等のスキームで表示させている」ような場合には対処する必要がある。簡単な対策としてはきちんとbaseURLを設定しておけばいいのだけど、そうすると今度はCSS、JS、画像などのバンドル内部に持っているリソースが使えなくなる。「外部から取得したHTMLをアプリ内のUIWebViewで表示したいけど、アプリ内の安全なリソースにはアクセスさせたい」場合に、NSURLProtocolで通信をフックする仕組みが使える。安全にローカルのリソースを読み込む前者の記事だと、NSURLProtocolのサブクラスでバンドル内のファイルを開いて、そのデータを返してしまう、というやり方をしている。後者の記事やPhoneGapの実装では、NSURLProtocolの「登録時の逆順に対応できるかどうかチェックする」「YESを返せばそこで止まり、NOを返すと次のNSURLProtocolのチェックに進む」という性質を利用して、「ホワイトリストに載ってないリクエストの場合は、canInitWithRequest:でYESを返した上でエラーなり空レスポンスなりを返し、ホワイトリストに載っている場合はNOを返して通常のhttpやfileプロトコルとして処理させる」という方法でサンドボックス外のリソースにアクセスさせないようにしている。まとめNSURLProtocolを使えばさくっと独自スキームを定義できるよWebViewみたいに細かく制御しづらいものの内部の通信もフックできるよセキュリティ面でも意味があるよという感じなので、是非試してみてください。その2前の記事で予告した通り、今度はNSRegularExpressionの話。正規表現でマッチした部分文字列を取得するまずNSRegularExpressionオブジェクトを作って、それのメソッドにNSStringのオブジェクトを渡す、という形で使う。説明するよりコード見た方が早い。"大丈夫だ、問題ない"}地味にややこしい。Rubyで書いたらこんなんで済むのに。$2endまぁRubyやPerlと比べるのは(少なくとも文字列操作や正規表現に関して言えば)フェアじゃないですけど!とにかくこれで正規表現で部分文字列を探せるようになりました、と。ちなみに、-firstMatchInString:options:range:というメソッド名で分かると思うけど、これは最初にマッチした箇所しか取ってこない。マッチした箇所全て欲しければ、-matchesInString:options:range:を使えば、NSTextCheckingResultが入ったNSArrayが返ってくる。別に返り値はずっと取っておく必要はなくて、単にマッチする毎になんか処理をしたいんだよ、ってときは、-enumerateMatchesInString:options:range:usingBlock:が使える。さっきの-firstMatchInString:options:range:を書き換えるとこんな感じになる。usingBlock:block];Blocksの使い方は以前書いた記事とか読んでもらえると分かるかもしれない。Leopardだけだったのだけど、今ならiPhone/iPadともに4系前提で作れるし、そもそもNSRegularExpression自体がiOS4.0以降にしか無いのでNSRegularExpressionを使える環境ならBlockも使えるので問題ない。置換する正規表現が使えるなら一番やりたいのは置換だろう、ということでもちろん置換もできる。-stringByReplacingMatchesInString:options:range:withTemplate:というのがそれ。NSLog(@"%@",replaced);最初は話を聞かなかったあいつもちゃんと一番いいのを頼んできたので、今度は大丈夫だろう。$0とか$1とか使ってるけど、もちろんちゃんと置換文字列の中でキャプチャした部分文字列を参照したりできてるはず。-stringByReplacingMatchesInString:options:range:withTemplate:は文字列そのものを置換してるわけじゃなくて、引数のNSStringのオブジェクトをcopyして置換したものを返してくる。元のstringは何も変わってないので変わったつもりで使おうとしたらアレ?ってなるし、毎回文字列のコピーをするので場合によっては無駄になる。その場合は-replaceMatchesInString:options:range:withTemplate:の方を使う。基本的には-stringByReplacingMatchesInString:options:range:withTemplate:と同じなんだけど、以下の点が違う。引数にNSStringでは無くNSMutableStringを取る引数のオブジェクトのコピーではなく引数のオブジェクト自体を置換する返り値は置換後の文字列ではなく整数値で、置換箇所の数を返すというわけで、ある正規表現で置換した文字列をさらに別な正規表現で置換して、みたいなことをやる場合はこっちのメソッドを使うべき。ちなみに、上記二つのメソッドはマッチした箇所を全部置換する。下のようなコードだと「大丈夫か」と「大丈夫だ、」が両方置換される。NSLog(@"%@",replaced);もしマッチした箇所の内特定の部分だけを置換したい場合は、-firstMatchInString:options:range:とか-matchesInString:options:range:でNSTextCheckingResultのオブジェクトを取得しておいてから、-replacementStringForResult:inString:offset:template:を使う、みたいな感じになるかしら。NSRegularExpression両方書いてみた感想で言うと、個人的にはRegexKitLiteのNSStringにメソッド生やしてくアプローチのAPIのが使い易いと思った。CoreFoundation使ってごりごり書いてるのでパフォーマンスも悪くないし、割と早い段階からBlocksに対応してたりとアクティブに開発されてるし、その気になればソース読めるし(まぁ、チラ見しては見たもののあんまり読む気にはならないのだけども)...とか考えると、既にRegexKitLiteを使ってるなら別に無理にNSRegularExpressionに乗り換える必要は無い気がしてくる。iOS4.0以前のバージョンもターゲットにするなら他に選択肢はないし、あと何故かNSRegularExpressionクラスはiOSにしか無くてMacOSXでは使えないという面白いことになってるので、iOSでもMacでも動くようなコードを書く場合もやっぱりNSRegularExpressionは使えない。言えNSRegularExpressionの方はFoundationの一部なので、数カ所正規表現での置換を使いたいが為に外部のコード落としてきてプロジェクトに組み込んでlibicucoreに忘れずにリンクして...ってしないで済むなぁとか、万が一iOSの内部の実装が変わったりなんかの規約が変わったりしてもおそらく書き換えないで済むだろうなぁという多少の安心感とかはある。これから作るアプリで、4.0以降のみをターゲットにしてる場合は、NSRegularExpressionを使って書こうかなぁなんて思ったりしてたり。その1ちょっと前に書こうと思ってて忘れてたネタ。Framework内でも地味に正規表現が使えるようになってきてるのでメモがてら記事にしておく。NSRegularExpressionSearchCocoaで文字列中に別な文字列が含まれているかどうかを知りたいときは、NSStringの-rangeOfString:というメソッドを使う。RubyのString#indexみたいな感じで、見付かった文字列がどこにあるかの位置を返してくれる。Found");}これにもう少し細かく色々なオプションを指定できる-rangeOfString:options:というメソッドがあるのだけど、iOS3.2以上のバージョンだとこのオプションにNSRegularExpressionSearchというのが指定できるようになっている。実際に使うときはこう。@"1日3食のうち4食はうどんを食べたいと思っている。Found");}rangeOfString:に正規表現(の文字列)を渡せるようになってちょっと便利。書式はICU-comaptibleだそうだけど、RegexKitLiteもlibicucoreを使ってるので、RegexKitLiteを使ってた人は得に気にすることなく使えると思う。methods.」だそうで。マッチした箇所を全部取ってくるとか置換するとかは別な方法でやるようだ。NSRegularExpressionさっきのはNSStringの文字列検索のオプションだったけど、正規表現そのものを扱うNSRegularExpressionというクラスがある。NSRegularExpressionSearchオプションは3.2以降であれば使えるけど、NSRegularExpressionクラスは4.0以降。今までiPadでは使えなかったので、RegexKitLiteを置き換えるには至らなかった。iPad版を含むiOS4.2がリリースされるので、ようやくiPadでも4系の機能が使えるようになるのです。いうことで次回はNSRegularExpressionを使った文字列検索を記事にします。「Objective-Cで、参照先のオブジェクトから参照元に通知を送る方法。」を読んてで思ったんですが、若干冗長な気がする。プロトコルを使うプロトコルを使うんだったら、最初からそのプロトコルに適合するオブジェクトしかdelegateになれないようにしちゃった方がいいと思う。delegate;@endこうしちゃえば、そもそもSampleDelegateに適合しないオブジェクトはdelegateになれないので、conformsToProtocolのチェックは必要なくなる。UIKitでも(例えばUITableViewのdelegateとか)大体そうなってますね。あと、ObjCはレシーバがnilの場合はメッセージ送信を単に無視するので、respondToSelectorとか送ってもYESが返らないのでこの場合はnitチェックもしなくていいと思う。notifyObjectChangedの定義はこれで十分。}}非形式プロトコル(カテゴリ)を使うプロトコルがoptionalなメソッドしか規定してなくて、あるメソッドを定義したデリゲートがある場合はそれを呼ぶけど、そうでない場合は無視するかデフォルトの動作をする、っていうような場合は、そもそもプロトコルで規定しないでカテゴリで実現しちゃう手もある。delegate;@endちなみにnotifyObjectChangedの実装は同じでいい。このタイプの例はNSURLConnectionとか。こっちの利点はプロトコルの宣言がいらないので、既存のクラスや外部のライブラリのクラスのインスタンスでもdelegateにできること。それが何であるかはどうだっていいんだ、ただ送ったメッセージに答えてくれる何かでありさえすれば、みたいなときにはこの方法でもいい。欠点は、プロトコルと違ってコンパイル時にdelegateが期待してるオブジェクトかどうかを解決できないこと。元記事の例みたいにoptionalなメソッドしか規定してない場合だと、プロトコルに適合してるからと言って期待してるオブジェクトかどうかははっきりしないので大差ないんだけど、仮にそうであってもプロトコル宣言を強制することによってデリゲートになり得るクラスを限定するってこともあるので、この辺はケースバイケース。Observingを使う手もある。something}@endこういう風にしておくと、fのhogeプロパティの値が変更されたときに、BarのインスタンスのobserveValueForKeyPath:ofObject:change:context:メソッドが呼ばれる。}というDictionaryが、contextにはNULLが渡ってくる。これの利点は、「Fooクラス側には全く手を加えなくていい」「あらゆるオブジェクトに対して、一貫したインターフェースで同じように設定できる」ということ。Cocoaのクラスだろうが外部のライブラリのクラスだろうが自分で作ったクラスだろうが同じように「あるプロパティの値が変更されたら教えてねー」っていう設定ができるし、参照先のオブジェクトが参照元のオブジェクトのことを気にする必要が無くなって関係性が緩くできる。Barの仕様を変更したくなってもFooをいじる必要はない。あと、delegateの場合と違って通知を受けとるオブジェクトが複数設定できるので、アプリケーションのあちこちが同時多発的に状態変化するみたいなことができる。forKey:@"hoge"]が呼ばれたときだけしか通知されない」ということ。Fooクラスの内部でインスタンス変数を直接弄って内部状態が変わったりしたときには通知されない。あとは、あらゆるオブジェクトに対して使えるので便利なんだけど、どのオブジェクトからの通知も必ずobserveValueForKeyPath:ofObject:change:context:を呼ぶので、沢山通知を設定するとobserveValueForKeyPath:ofObject:change:context:の中身が大分カオスなことになるので、ご利用は計画的に。他にはNSNotificationを使う方法もあるけど、多用すると処理がどこでどうなるかわかりづらくなるので割と慎重に使った方がいいかも。Observingでもそうなんだけど、NSNotificationの方がより汎用性が高いのでよりこんがらがり易くてよくハマる。setCanCancelContentTouches:NO];って書き方があってこれみんなどういう使い分けしてるんだろうなぁ。poohtarouの日記セッターというか、ドット記法の話かな。ドット記法と普通のメソッドの使い分けまぁ、まずはこんなクラスがあったとします。*title;@endこのクラスのオブジェクトを作ってtitleを設定/参照するコードはこんな感じです。title]);これは上と下どっちのコードも同義。ドット記法でアクセスすると、setTitle:メソッドやtitleメソッドが呼ばれる。getter/setterを自動生成させないで自分で実装した場合もちゃんとそのメソッドを呼んでくれる。book.title);これはコンパイルエラーになる。id型のオブジェクトのメンバにドット記法でアクセスしようとすると怒られる。いやid型じゃなくてちゃんとクラスを明示して変数宣言すればいいじゃんて?NSArrayのobjectAtIndex:やNSDictionaryのobjectForKey:は返り値の型がid型ですよね。キャストすればいい?だって中に入ってるのがBookクラスのオブジェクトかどうかわかんないじゃん。isKindOfClass:で調べてからキャストする?いやいや、そのArrayの中にはGameクラスのオブジェクトも一緒に入ってて、GameクラスもsetTitleできるからそこは区別なく扱えた方が便利なんだよ!...みたいなこともあるわけで。そんなときはこう書くかなぁ。上"];}はいできた。これでBookクラスのオブジェクトをid型の変数で受けてもちゃんとsetTitle:できました。ついでにsetTitle:メソッドを実装してるオブジェクトであれば、Bookクラスと継承関係になくてもsetTitle以外は全然無関係のインターフェースを実装してようとも同じように扱えます。アヒルのように鳴くものはアヒルではなく隣のおばあちゃんでした。なかった、アヒルのように鳴くものはアヒルです。setTitle:@"本当は怖いグリム童話"];}って書いてもコンパイル通るし実行時にエラーも出ない。いう場合はプロトコルを定義してあれば(そしてobjがそのプロトコルに適合してれば)警告が出ない。setTitle:@"本当は怖いグリム童話"];簡単にまとめると、明示的にクラス名を指定して変数を宣言してるときはドット記法で、id型で受けるべき時はメソッドでっていうのが一応の使い分けかなー。元記事の例の場合はscrollViewはUIScrollView型で変数宣言してて、そのコンテキストではUIScrollViewであることがはっきりしてるのでドット記法でいいと思う。具体的なクラスを想定しているわけではなくて、あるアクセサを持っている何かのオブジェクトっていう扱いをするときは、respondToSelector:とperformSelector:を使うか、プロトコルを定義しといてメソッド呼び出しするか、って感じになると思う。Condingところで、ObjCにはドット記法とメソッド呼び出しの他にもう一つ、オブジェクトのメンバにアクセスする方法がある。2.0になる前からある。さっきのBookクラスのオブジェクトに対してだったらこんなことができる。valueForKey:@"title"]);恐ろしいですね。オブジェクトがDictionaryか何かのよう。これはNSObjectの子孫にあたる全てのクラスで使える。これの恐しいのは、アクセサメソッドやプロパティが宣言されてなくても使えてしまうということ。valueForKey:@"title"だったら、titleメソッドが定義されてたらそれを呼ぶtitleメソッドが無くて、getTitleメソッドが定義されてたらそれを呼ぶどっちも無くて、インスタンス変数titleが宣言されてたらそれを返すどれも無くて、インスタンス変数_titleが宣言されてたらそれを返すっていう順番で解決されるので、Bookクラスの場合titleをプロパティとして宣言してる、つまりtitleメソッドが定義されてるのでそれが呼ばれる。プロパティとして宣言してなくても、title変数があるので3番目でひっかかってそれが返る。ちなみにインスタンス変数が@privateで宣言されてても関係ない。1、2はともかく3、4は大分アレげなので、内部でしか使わない意図せず変更して欲しくないインスタンス変数は@privateで宣言した上でname_とかして上のルールにひっかからないようにしておく癖を付けたほうがいいと思う。外部からアクセスしてもいいやつでも名前は変えた上で明示的にプロパティで宣言した方が安全かなー。元記事の話とは直接関係無いけど余談でした。Coding」とかで調べてみるといいと思う。これはこれで便利だし、AppKitとかUIKitとかでは結構使われてるので。追記そうだブコメで指摘をもらってたんで追記しようと思ってたのすっかり忘れてた。Codingを一番使うであろう場面はCoreDataのNSManagedObjectですね。CoreDataでエンティティを定義してデータをつっこんだり取得したりするときは、NSManagedObjectかそれを継承したクラスがモデルオブジェクトになる。サブクラスを作るときはプロパティを定義しとけばいいんだけど、NSManagedObjectにはもちろん中のデータへのアクセサは定義されてない。するかと言うと、valueForKey:とsetValue:forKey:で値の取得や変更をするってことになる。Observingについて調べておくといいと思います。Dispatch(GCD)も試してみる。関係ないけど、グランド・セントラル・ディスパッチってなんか必殺技っぽいよね。一緒に高らかに叫んでみようか。ーの、グランド!セントラル!ディスパッチ!!GCDってなにさドキュメント嫁。...だけだと流石に不親切なので、一応簡単に説明すると、APIを通してぽんぽん処理をqueueにつっこんでってやると、ランタイムの方でそれを上手いこと並列実行しといてやるよ!安心しろチェリーボーイ共、スレッドのことは俺が面倒見てやるぜ!って仕組み。そんな口調なのかどうかはわかんないけど、まぁ大体そんな感じ(適当)。例によってRubyと比較まぁ、こんなコードがあったとします。tんで、それを例によってObjCでBlocksを使って書くとこんなコードになります。ちなみにどっちも別にBlocksやlambdaでやる必要は無いんだけど、この後のコードと比較の為にわざとそうしてるのでスルーしておくれやす。timeIntervalSinceDate:d]);まぁ見ての通り、標準出力に一言言ってから0.25秒sleepするだけの簡単なお仕事を20回ほどやってもらってます。あたり前のことだけど、0.25*20で5秒+αくらいの時間がかかるし、0.25秒っつってんのに5秒待たせるとか相当いい加減なやつだ。こんな風に、それぞれの処理が独立してるけど一個一個は結構時間かかる、みたいなのは、並列に実行させちゃったらいいんじゃね、みたいなことを偉い人は言いました。tまだ若干怠けてるけど、まぁ0.3秒行かない程度で終わる。5秒に比べたら一瞬みたいなもんだよね。次はそれをObjCで...と言いたいところなんだけど、ObjCで上のRubyのコードをNSThreadってクラスを使って書こうとすると、割と面倒い。特定のコンテキストを別スレッドで実行しようと思うと、detachするのにtargetとselector、つまりスレッドで実行されるオブジェクトとそいつから呼び出されるメソッドがなくちゃいけない。もって、作ったスレッドを自分で管理しなきゃいけない。一応適当なサンプルは書いたけど、あんまりこれをObjCで自前で書くことは無いと思う(理由は後述する)。GCDです最初の方に言ったけど、大分ざっくり言うとGCDってのは「処理のブロックをキューにつっこんでってやると裏で上手いこと並列に処理してくれる」ものです。要は並列処理のめんどい感じを多少楽にしてくれるのがGCDの兄貴だってことです。兄貴なのか姉貴なのかは知りませんが。どっちかというと僕はお姉さんが好きですがどうでもいいことです。timeIntervalSinceDate:d]);はい。Rubyの方をThreadを使って書き直したときと同じく、0.3秒行かないくらいの時間でさくっと処理してくれました。コメントアウトしてるのを戻せば、ちゃんと別々のスレッドで動いてるのも確認できると思います。ちなみにObjCの文法で書いてるとこを削れば普通にCでも使えます。<dispatch/dispatch.h>してください。変わったところを解説すると、まずブロックの型がdispatch_block_tに変わってる。(^)(void)、つまり何も取らず何も返さないブロックって定義になってるので実はさっきと何も変わってない。次にdisptach_group_createをしてgroupを作ってるけど、これはまぁ名前通り非同期に実行する処理をグルーピングするためのもの。Rubyの方でもThreadGroupが出てきたけど、あれと一緒で最後のdispatch_group_waitで一連の処理が全部終了するまで待ってやる為に使う。今回は全部の処理が終わるまでの時間が見たかったのと、アプリケーションとかじゃない普通のCUIのコマンドとして作ったときに何も考えずに非同期処理させるとdispatchしたのが終わる前にmainが終わっちゃうのでwaitする必要があったけど、GUIアプリケーションとかデーモンとかだとその心配はないのでグルーピングせずに単にdispatch_asyncしちゃってもいい。ここからが本質、dispatch_get_global_queueとdispatch_async(またはdispatch_group_async)。いっても別に大したことではなくて、queueを用意しますdispatch_asyncにqueueとblockを渡しますあとは裏でよしなにやってくれます以上。中では「システムの負荷を見てスレッドを作るか待つか決める」「スレッドが一個も空いてなければ作るけど、さぼってるやつがいたら再利用する」「あっちこっちから放り込まれたブロックをどのスレッドに割り当てたら効率良いか考える」とか色々やってんだけど、使う側としてはそんなこと気にする必要無いし、それどころかスレッドが作られてることすら隠蔽されてる。やったのは単に関数にブロックを渡しただけ。ゆとりの僕でもできる簡単なお仕事です。ちなみにdispatchしてwaitするあたりの処理を続けて何回も実行すると、ちゃんとスレッド再利用してるのが確認できる。他にも、globalって名前が付いた関数があるからにはglobalじゃないqueueを作る関数もあるとか、asyncって名前が付いた関数があるからにはsyncして実行する関数もあるとか、メインスレッドで動作するqueueがあるとか、さっきのコードではわざわざforループ回したけどループにはループ専用のdispatch_applyがあるとか、まぁ色々あるんだけど、Xcodeのあのアホみたいに使い辛いドキュメントビューワの検索窓にdispatch_って入れてやるといっぱい出てくるので見てみてくれればいいかと思います。ここまでは誰がどうみてもCの関数、っていうかさっきも書いた通り実際<dispatch/dispatch.h>をincludeすればCでも使えるAPIです。いやさ、確かにObjCの文法はキモいけどさ、せっかくObjCで書いてるのにCの関数使うってどうなのよって?ご心配なく。ObjCならObjC流に実装する方法はもちろんある。「並列実行」「どんどんキューにつっこむ」「スレッドの面倒はキューが見てくれる」あたりで、LeopardまでのOSXとかiPhoneSDKとかでアプリを書いたことある人は「それNSOperationとNSOperationQueueでできるじゃん」と思ったはず。これもキューにオペレーションオブジェクトをどんどんつっこんで行けば非同期でよしなにやってくれるクラスで、それ自体はGCDやBlocksとは関係なく前から使える。自前でNSThreadを作ったり管理したりはやったことなかった。ObjCだと何も変わらないじゃんと思ったけど、Blocksが導入されたことでNSArrayやNSDictionary同様便利な機能が追加されているという。timeIntervalSinceDate:d]);おお、これは便利だな。addOperationWithBlock:]を使えばブロックを渡してやるだけで非同期実行できちゃう。setCompletionBlock:]ってメソッドが追加されてて、「処理を一括りにして並列実行する」コードが大分書きやすくなっている。凄いな、ほんと、どこまで行くんだろう。ついでにBlocks入門とNSArrayにmap生やしたのとこの記事のコードをのサンプルはgistに上げてみたので、まぁ一応一通り動く例になってるはず。あーあと簡易ベンチマーククラスみたいのも作ってみたので入れといた。適当なので実用するのはおすすめしません。Leopardでrubyとrakeが入ってる環境なら、cloneしてきてrakeすればコンパイルできるはず。Blocks入門に引き続き、Blocksネタ。そっちの記事ではBlocksはクロージャ的ななにかだって言ってるのに単なる関数ポインタみたいにしか使ってなかったので、せっかくなのでクロージャ的に使ってみる。enumerateObjectsUsingBlock:]を使えば、Array#each相当のことができるとこまでは前回の記事でできた。すると、Array#map相当のこともNSArrayにさせられるはず。}Rubyistには説明の必要もないと思うけど、Array#mapが何をしてるかというと、1引数のブロックを受けて自分自身の要素を一つずつブロックに渡してその返り値を要素にした新しいArrayを作って返す(※自分自身は変更しない)みたいなところ。それをObjCで実装する。ベタに書くとこんな感じ。array;}@endこれはこれでなんかスッキリしてていいんじゃねって気がしてきた。enumerateObjectsUsingBlock:を使って書き直してみる。array;}ちょこっと説明する。ブロックは、RubyやPerlのそれと同じく、生成されたコンテキストにある変数を参照できる。上のコードで言うとnewArrayはブロックの中じゃなくて外で宣言されてるけど、ブロックはその時点でのコンテキストを持ってるので、ブロックを生成したスコープで見えるものはブロック内でも同じように見える。使う側ではこんな風に書けるようになる。stringWithFormat:@"%@ー!俺だーッ!結婚してくれー!",item];}];おお。それっぽいそれっぽい。__blockちょうどタイミング良く昨日弾さんとこで同じような話をしてたんだけど、上の例はしれっとブロックの外の変数に破壊的操作をしてるけど、例えばこういうのはできない。f;}ObjCのBlocksの実装だと、何も指定してないとブロックの外の変数は「見える」だけで「変更できない(ブロックの中から再代入できない)」。__block修飾子を付けて変数を宣言すると、ブロックの中から変数を変更できるようになる。ブロックが解放されちゃってるはず)。Block_copy(f);}こんな感じになる。10.6だったらCのコードとしてもObjCのコードとしてもコンパイルできるけど、ObjCの場合は<Foundation/Foundation.h>を、Cの場合は<Block.h>をimoport(include)する必要がある。あと、Block_copyして関数のスコープ外で使えるようにしたときは、当然呼び出し側で責任持ってBlock_releaseしてブロックを開放してやる必要がある。ここでblockも開放されるちなみになんでNSMutableArrayの方は破壊的な操作をできるのかというと、多分変数の操作じゃなくてオブジェクトへのメッセージ送信をしてるだけだから。count]とかやるのと意味的には同じなのでできるってことじゃないのかな。変数自体をいじろうと怒られるはず。Dispatchで遊んでみた編。Blocks入門を見て思い出した。すいませんタイトルは便乗です。試しに書いてみるBlocksってのが何者なのかはさっきの記事なりAppleのドキュメントなりを見てもらえばいいと思うんですが、まぁウケが良さそうなので付けたタイトルにのっとってRubyと比較してみる。x);};f(@"うどん");なんだ、そっくりじゃない。上がRuby版、下がObjC版。下は普通引数にはNSString*とかを使うと思うけど、まぁRuby版と挙動を合わせるためにidにしてみた。それぞれコピペして動かしてみるといいよ。好きな子の名前とか入れてみるといいよ。なに?ブロックを変数に入れてるとこの宣言がキモい?じゃあこうだ。x);};id型の変数に代入できてしまった。実はこのブロックはオブジェクトなので、普通にid型として扱えるし、メッセージのレシーバになったりできる。(^)(id))f)(@"うどん");とかキャストしてやらなきゃいけないわけで、id型で宣言する意味はあんまりない。id型として扱えるってことは、NSArrayとかNSDictionaryにつっこんどいたりできるってことでもある。コレクションクラスとか自前で作らなくても他のオブジェクトと一緒にArrayにつっこんどいてうまいこと処理するとかできる。ところでObjC上ではObjCのオブジェクト扱いだけど、Cで使ってるときはこれ何として扱われてるんだろう。ブロック構文これだけだと何それおいしいので終わりなので、実際に使ってるところをみてみる。Rubyでblockって言ったらブロック構文を思い浮かべると思う。"#{l}なら多分書ける"endBlocksと言うからにはこういう使い方をするメソッドがいくつかFoundationのクラスにもある。上記のコードをObjCでブロックを使って書くとこんなかんじになる。enumerateObjectsUsingBlock:ってのが引数にブロックを取るNSArrayのインスタンスメソッドで、Array#eachと同じように自分自身の要素を一個ずつ順にブロックに渡して実行してくれる。ちなみに、「最初の引数がNSArrayの要素のオブジェクト、次がインデックスの整数値、最後がループを止めるためのBOOLのポインタ」という3引数のブロックを渡さないといけない。どっちかっつーとEnumerable#each_with_indexのが近いかな。上の例ではitemしか使ってないけど、全部使うとしたらこんな感じ。3個目の引数にYESを入れてやるとそれ以降は実行されなくなる。