As Sloth As Possible

可能な限りナマケモノでありたい

タグ:iOS

「スマートフォンについて何か喋ってよ」というかなり大雑把なネタ振りをされたので、第五回ライブドア・テクニカルセミナーでこんな感じの話をしてきました。

あとで動画や資料は公開されるはずですが、大変申し訳ないことにテンパりまくって見るに耐えない感じになっちゃってると思うので、一応何を言いたかったのかを補足しておきます。

iOSとAndroidは違うものだという話

開発環境の違いについてはまぁいいとして、例えばアプリの設計思想。iOSでは「まずアプリケーションという大きなプログラムがあって、その中で画面を表示したり通信したりしてる」って感じの構成になってるのだけど、Androidの場合「画面とかデータ管理機能とかそういう独立した部品があって、それらをまとめたものに名前を付けてアプリケーションという風に見せる」という感じになっていて、後者の方が若干まどろっこしい感じはある。一方でアプリ間でのやりとりについて考えてみるとiOSでは「openURLで他のアプリを起動する」とか「Keychainだとかアルバムだとかを使って(限定的に)データを共有する」とかその程度に留まっているのだけど、Androidの場合はOSが提供する機能も同じアプリ内の機能も他のアプリの機能も全て「部品」という形になっているので、きちんと設計しさえすればデータや機能をシームレスに連携させられて大分可能性が広がる。この考えかたの違いはアプリの作り方の違いにモロに出てくる。

それから、デバイスの違い。iOSの場合はiPhone/iPod touch系とiPad系の二系統しかなく、どちらもApple一社が開発しているので、OSや周辺サービスも含めたプラットフォーム全体のコンセプトから良くも悪くも外れない。一方のAndroidはと言うと、Googleが主導してはいるものの、端末メーカー各社それぞれがOSにカスタマイズを加えてたり、独自のハードウェア(キーボードが付いてたり、先行してFeliCa対応してたり、裸眼立体視ディスプレイが付いてたり、そう言えばゲームのコントローラが付いてたりするようなのも出るんだっけ?)を搭載してたり、小さな画面のものがあったり、横固定だったり、多種多様だ。ハードウェアの違いがソフトウェアに影響を与えないわけが無く、「使い易いアプリって何?」って問いの答えはiOSとAndroidでは当然違うし、Android端末間でも異なってくることもある。

ここまでは技術者視点だけど、当然ビジネス的にも文化的にも異なる展開をするだろうね。と、いうことを考えたら、アプリの企画や開発をするのに「iOSとAndroidは違うもの」って認識は当然持っておく必要はあるだろう。

iOSアプリとAndroidアプリはどっちが作り易いか

率直に言うと、最初のとっつき易さに関して言えばAndroidの方が圧倒的に上。僕の元々のお仕事はPerlやRubyでWebアプリケーションを開発することで、今もアプリ作りのかたわらWebやってるのだけど、そういう人が、ObjCとXcode/IBでアプリを作りクローズドなプラットフォームでお仕事するのと、JavaやJVM上で動く言語とEclipse+ADTでアプリを作りオープンなプラットフォームでお仕事するのとで、どっちが大変かなんてのは言うまでもない。

で、だからAndroidのが素晴しいって話になるのかというとそれはそれで違うんだよってのが、無茶苦茶RTされまくってハッシュタグを埋めてしまった件のスライド(本当にすいませんでした!)の意図。「最初は薔薇色の世界に見えるAndroidも作り続けて行くと段々難しさに気付いていく」「最初はとても理解できないと思ったiOSも作り続けて行くと良さに気付いていく」「だからつまり、最初のとっつき易さだけでどっちかが圧倒的に優れていると言えるわけではない、最初のハードルを越えたら別の問題が見えてくる」っていうのを(一部の人には)視覚的に分かってもらえるように、と思ってあのイラストを入れたんだけど、前日にまどか☆マギカの8話を観て「正直やりすぎた」と反省はしました。あのスライドを見て「きっとこの人はAndroidよりiPhone派で、あと赤い子が好きなんだろう」ってコメントしてる人がいて、いやうん割とその通り(「杏子に『食うかい?』って言ってもらう」ことを願って白い獣と契約しかねないです)だけど、いくらなんでもアレに例えなきゃいけないほどAndroidを悪くは思ってない。Androidが孵す卵はもう少し夢のあるものだって信じてますし、iOSのあの林檎を輝かせ続けるために何が犠牲になってるかに思い至らないわけでもないですし。

魔法少女の話は置いといて、じゃあその「別の問題」ってなんなの、という話でいうと、まず一つはマルチデバイス/OS対応。Androidは自由度の高さが最大の売りだけど最大の欠点でもあって、Androidに取り組んでるところは軒並「端末やキャリアによってお約束事が違い戸惑う」とか「バージョンアップしない端末があってどのバージョンのOSを対象にするか迷う」とかそういうことに悩んでいる。正直、Android開発に関わってない人が想像してる以上にデバイスごとの差異が大きいし、今後収束するどころかもっと個性的なものが増える方向に進むだろう、だってそれが一番の売りなんだから。それに真面目に対応していくことの面倒臭さは、iOSアプリの最初の学習コストを上回ると思う。iOSだってもちろんiPhoneとiPadで両対応ってのは凄く大変だし、最近はケータイだと思って買っていくので家にパソコンも無くアップデートしないみたいな人も増えてると聞くので、その問題からは無縁じゃないんだけど、それでも現時点ではAndroidよりは幾分マシだ。

それから、パフォーマンスチューニング。いくらスマートだスマートだって言ったって所詮はモバイルデバイスなので、タイトなリソースをやりくりしていかなきゃいけない。今まで組み込み機器やローレイヤーなところの開発をしてた人からすれば「何を贅沢な」「ゆとりめ」と冷笑されそうな話だけどさ。スマートフォンアプリに求められる「リッチさ」はどんどん上がって行く一方だけど手持ちの戦力は高性能な戦闘機と言ったところで、その気になれば空母の艦隊を投入できるサーバサイドの開発とは全然違う。これに関して言えばiOSでもAndroidでも同じで、そういう段階に至ったらどっちにしてもそれなりに「難しい」。

クロスプラットフォーム開発に関して思うこと

きっと伊藤直也さんが肯定的な文脈で言及するに違いないと思って(実際してた)内心ビクビクしながら話してたんだけど、どうしても言わざるを得なかったので言ってしまった。一つのコードで両方のプラットフォームに対応するのは、現時点ではあまり現実的じゃないと思う

もちろんアリだろうと思ってる領域もあって、例えばゲームエンジンみたいなものは、凄く効果が高いと思う。最近は一般的な携帯ゲーム機向けのものと同じくらいのクオリティの高いゲームがどんどん増えてるけど、ああいうのは大概UIから自前で作ってしまうし、中のロジックもiPhoneだからAndroidだからということも無いだろうから、細かい差異はライブラリに吸収させちゃうのが定石だろう。それから、コアな部分をHTML5+JSで作って、ブラウザ上でできないことやパフォーマンスが必要なことをやらせるためにネイティブのUIでWebViewをラップする、というハイブリッド戦略も良いと思う。SNSとかブラウザゲームとかだと期間限定のイベントがあったり速いペースで機能改善していったりする必要があって、それで言うとアプリ開発して(iOSならAppleの審査を通して)ユーザにアップデートしてもらう、というプロセスを通すと遅すぎて全然上手くいかない。うちで作ってるアプリだとロケタッチとかは部分的にそんなアプローチをしてるし、今後そういうアプリも増えると思う。

懐疑的なのはトランスレータとかミドルウェアとかの話。Titaniumとかが盛り上がってるのはもちろん知ってるし追ってるけど、最近だと「一つのコードで両方のプラットフォームに対応」みたいな話より「iPhone(or Android)のアプリ開発を楽にする」って文脈で語られることのが多くなってきてるかなーという印象を受ける。まだまだ発展途上でどっちかのプラットフォームのサポートは手厚いけどもう片方は追いついてないだけで、今後は次第に改善していくだろう、ってところもあったりするんだけど、それとは別に個人的には「これだけ"違い"があるんだから、無理して同じ扱いをするよりそれぞれに合ったやりかたをした方がいいんじゃないか」って思ってる。特にツール系のアプリはそうで、それぞれにUIのお作法があるのでちゃんとそれぞれの特性を活かしたUIにした方がよくて、そうなったときに内部のロジックだって無理に同じにしておくより別プロジェクトにしておいた方がかえってメンテしやすかったりする。将来的にどうなるかは分かんないけども、今現在両方のアプリを作ってての実感としてはそんなところ。

というところまで書いて

そんな話が全然出来てなかった自分の話下手っぷりに絶望したのでもう少し練習しないとな、と思いました。あたしって、ほんとバカ。

謝辞

キュゥべえドロイドくんはSENさんに、杏子の林檎はニチロさんに描いてもらいました。素敵なイラストありがとうございました!

前の記事で予告した通り、今度はNSRegularExpressionの話。

正規表現でマッチした部分文字列を取得する

まずNSRegularExpressionオブジェクトを作って、それのメソッドにNSStringのオブジェクトを渡す、という形で使う。まぁ説明するよりコード見た方が早い。

NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
NSError *error   = nil;
NSRegularExpression *regexp =
  [NSRegularExpression regularExpressionWithPattern:@"「そんな(.+)で大丈夫か?」「(.+)」"
                                            options:0
                                              error:&error];
if (error != nil) {
  NSLog(@"%@", error);
} else {
  NSTextCheckingResult *match =
    [regexp firstMatchInString:string options:0 range:NSMakeRange(0, string.length)];
  NSLog(@"%d", match.numberOfRanges); // 3のはず
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]); // マッチした文字列全部
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]); // "正規表現"
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]); // "大丈夫だ、問題ない"
}

地味にややこしい。Rubyで書いたらこんなんで済むのに。

# coding: utf-8
if "「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」" =~ /「そんな(.+)で大丈夫か?」「(.+)」/
  puts $&
  puts $1
  puts $2
end

まぁRubyやPerlと比べるのは(少なくとも文字列操作や正規表現に関して言えば)フェアじゃないですけど!とにかくこれで正規表現で部分文字列を探せるようになりました、と。

ちなみに、-firstMatchInString:options:range:というメソッド名で分かると思うけど、これは最初にマッチした箇所しか取ってこない。マッチした箇所全て欲しければ、-matchesInString:options:range:を使えば、NSTextCheckingResultが入ったNSArrayが返ってくる。別に返り値はずっと取っておく必要はなくて、単にマッチする毎になんか処理をしたいんだよ、ってときは、-enumerateMatchesInString:options:range:usingBlock:が使える。さっきの-firstMatchInString:options:range:を書き換えるとこんな感じになる。

NSRegularExpressionOptions options = 0;
NSRange range = NSMakeRange(0, string.length);
id block = ^(NSTextCheckingResult *match, NSMatchingFlags flag, BOOL *stop){
  NSLog(@"%d", match.numberOfRanges);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]);
  NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]);
};
[regexp enumerateMatchesInString:string options:options range:range usingBlock:block];

Blocksの使い方は以前書いた記事とか読んでもらえると分かるかもしれない。あの記事を書いた時点ではiOS4.0を想定してアプリ作れなかったので実質まともに使えるのがSnow Leopardだけだったのだけど、今ならiPhone/iPadともに4系前提で作れるし、そもそもNSRegularExpression自体がiOS4.0以降にしか無いのでNSRegularExpressionを使える環境ならBlockも使えるので問題ない。

置換する

正規表現が使えるなら一番やりたいのは置換だろう、ということでもちろん置換もできる。-stringByReplacingMatchesInString:options:range:withTemplate:というのがそれ。

  NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
  NSString *template =
    @"$0\n→($2砕け散る)\n→「神は言っている、ここで死ぬ運命ではないと」\n→「$1」「一番いいのを頼む」";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"「(そんな(.+)で大丈夫か?)」「.+」"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);

最初は話を聞かなかったあいつもちゃんと一番いいのを頼んできたので、今度は大丈夫だろう。しれっと$0とか$1とか使ってるけど、もちろんちゃんと置換文字列の中でキャプチャした部分文字列を参照したりできてるはず。

ただ、-stringByReplacingMatchesInString:options:range:withTemplate:は文字列そのものを置換してるわけじゃなくて、引数のNSStringのオブジェクトをcopyして置換したものを返してくる。なので、元のstringは何も変わってないので変わったつもりで使おうとしたらアレ?ってなるし、毎回文字列のコピーをするので場合によっては無駄になる。その場合は-replaceMatchesInString:options:range:withTemplate:の方を使う。基本的には-stringByReplacingMatchesInString:options:range:withTemplate:と同じなんだけど、以下の点が違う。

  • 引数にNSStringでは無くNSMutableStringを取る
  • 引数のオブジェクトのコピーではなく引数のオブジェクト自体を置換する
  • 返り値は置換後の文字列ではなく整数値で、置換箇所の数を返す

というわけで、ある正規表現で置換した文字列をさらに別な正規表現で置換して、みたいなことをやる場合はこっちのメソッドを使うべき。

ちなみに、上記二つのメソッドはマッチした箇所を全部置換する。例えば下のようなコードだと「大丈夫か」と「大丈夫だ、」が両方置換される。

  NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」";
  NSString *template =
    @"チョ☆チョニッシーナ☆まっソコぶれっシュ☆エスボグリバンバーベーコンさんだね!";
  NSRegularExpression *regexp =
    [NSRegularExpression regularExpressionWithPattern:@"大丈夫(か|だ)、?"
                                              options:0
                                                error:nil];
  NSString *replaced =
    [regexp stringByReplacingMatchesInString:string
                                     options:0
                                       range:NSMakeRange(0,string.length)
                                withTemplate:template];
  NSLog(@"%@",replaced);

もしマッチした箇所の内特定の部分だけを置換したい場合は、-firstMatchInString:options:range:とか-matchesInString:options:range:でNSTextCheckingResultのオブジェクトを取得しておいてから、-replacementStringForResult:inString:offset:template:を使う、みたいな感じになるかしら。ちょっと面倒な気もするけど。

RegexKitLite or NSRegularExpression

両方書いてみた感想で言うと、個人的にはRegexKitLiteのNSStringにメソッド生やしてくアプローチのAPIのが使い易いと思った。CoreFoundation使ってごりごり書いてるのでパフォーマンスも悪くないし、割と早い段階からBlocksに対応してたりとアクティブに開発されてるし、その気になればソース読めるし(まぁ、チラ見しては見たもののあんまり読む気にはならないのだけども)…とか考えると、既にRegexKitLiteを使ってるなら別に無理にNSRegularExpressionに乗り換える必要は無い気がしてくる。iOS4.0以前のバージョンもターゲットにするなら他に選択肢はないし、あと何故かNSRegularExpressionクラスはiOSにしか無くてMacOSXでは使えないという面白いことになってるので、iOSでもMacでも動くようなコードを書く場合もやっぱりNSRegularExpressionは使えない。

とは言えNSRegularExpressionの方はFoundationの一部なので、数カ所正規表現での置換を使いたいが為に外部のコード落としてきてプロジェクトに組み込んでlibicucoreに忘れずにリンクして…ってしないで済むなぁとか、万が一iOSの内部の実装が変わったりなんかの規約が変わったりしてもおそらく書き換えないで済むだろうなぁという多少の安心感とかはある。ので、これから作るアプリで、4.0以降のみをターゲットにしてる場合は、NSRegularExpressionを使って書こうかなぁなんて思ったりしてたり。

ちょっと前に書こうと思ってて忘れてたネタ。iOSアプリ内で正規表現を使ってごにょごにょしようと思ったらRegexKitLiteを導入するのが一番てっとりばやいのだけど、iOS 3.2以降はFoundation Framework内でも地味に正規表現が使えるようになってきてるのでメモがてら記事にしておく。

NSRegularExpressionSearch

Cocoaで文字列中に別な文字列が含まれているかどうかを知りたいときは、NSStringの-rangeOfString:というメソッドを使う。RubyのString#indexみたいな感じで、見付かった文字列がどこにあるかの位置を返してくれる。こんな感じ。

NSString *string = @"I love Udon.";
NSRange match = [string rangeOfString:@"Udon"];
if (match.location != NSNotFound) {
  NSLog(@"Found: %@",[string substringWithRange:match]);
} else {
  NSLog(@"Not Found");
}

これにもう少し細かく色々なオプションを指定できる-rangeOfString:options:というメソッドがあるのだけど、iOS3.2以上のバージョンだとこのオプションにNSRegularExpressionSearchというのが指定できるようになっている。実際に使うときはこう。

NSString *string = @"1日3食のうち4食はうどんを食べたいと思っている。";
NSRange match = [string rangeOfString:@"[0-9]+食" options:NSRegularExpressionSearch];
if (match.location != NSNotFound) {
  NSLog(@"Found: %@",[string substringWithRange:match]);
} else {
  NSLog(@"Not Found");
}

rangeOfString:に正規表現(の文字列)を渡せるようになってちょっと便利。書式はICU-comaptibleだそうだけど、RegexKitLiteもlibicucoreを使ってるので、RegexKitLiteを使ってた人は得に気にすることなく使えると思う。

これだけでも大分マシにはなったんだけど、さっきのサンプルコード見て分かる通り最初にマッチした部分しか取ってこれないし、もしかして-stringByReplacingOccurrencesOfString:withString:options:range:とかにも正規表現使えるのかなとwktkしたのだけど、「You can use this option only with the rangeOfString:... methods.」だそうで。マッチした箇所を全部取ってくるとか置換するとかは別な方法でやるようだ。

NSRegularExpression

さっきのはNSStringの文字列検索のオプションだったけど、正規表現そのものを扱うNSRegularExpressionというクラスがある。NSRegularExpressionSearchオプションは3.2以降であれば使えるけど、NSRegularExpressionクラスは4.0以降。つまり今までiPadでは使えなかったので、RegexKitLiteを置き換えるには至らなかった。

が。そろそろiPad版を含むiOS4.2がリリースされるので、ようやくiPadでも4系の機能が使えるようになるのです。弟の仇をトルノデス。ということで次回はNSRegularExpressionを使った文字列検索を記事にします。予告。

↑このページのトップヘ