2011年1月9日日曜日

Python で 5 分でちょっとした XML ファイルを生成する

なんだか西尾先生が面白そうなことをしてるので、便乗してみることにしました。


■課題

今お仕事で iOS のアプリを書いているのですが、その中で次のような plist ファイルを作成する必要が出てきました。 plist ってのをご存じない方は、要するに XML ファイルだと思ってください。
    <dict>
<key>emojiName</key>
<string>表情(嬉しい)</string>
<key>emojiCode</key>
<string>0xE415</string>
</dict>
<dict>
<key>emojiName</key>
<string>表情(にこにこ)</string>
<key>emojiCode</key>
<string>0xE056</string>
</dict>
<dict>
<key>emojiName</key>
<string>表情(笑顔)</string>
<key>emojiCode</key>
<string>0xE057</string>
</dict>
こんな感じで全部で400項目ぐらい。Webページの資料があるので、そこから転載して上記のような plist にするお仕事です。

私がやってもいいのですが、あいにく手が回りません。そこで最近弊社に入社してきました新人さんにお願いすることにしました。ところがどっこい、本当に一個ずつ手で書いているからなかなか作業が進みません。ほかのお仕事もあるのですが、二日でやっと5分の1程度。これはどげんかせんといかんですね。


■さっそくやってみよう

まずは下準備として、ExcelにWebページから情報を貼り付けて整理します。Excelを中間エディタとして使うことで、行と列の操作が簡単になりますし、そのままコピペするだけできれいに表が完成します。整理し終わったら、Excelからテキストエディタにコピペすると、以下のような TSV (Tab Separated Value) になります。
猫 0xE04F
いぬ 0xE052
ねずみ 0xE053
うさぎ 0xE52C
あとはこれを XML にするだけです。さっそくPythonでやってみましょう。
#!/usr/bin/env python

data = u"""
ここに先ほどのTSVをコピペしてください
"""
template = u"""
<dict>
<key>emojiName</key>
<string>%s</string>
<key>emojiCode</key>
<string>%s</string>
</dict>
"""

lines = data.strip().sprit('\n')
for line in lines:
t = line.sprit('\t')
print template % (t[0], t[1])

すごい原始的なコードですが、これでも十分動きますし簡単です。ってあれ、これ元ネタの西尾先生のコードとほとんど同じじゃないですか><


■その結果

まぁそんなこんなで無事 Python のコードもできたので動かしてみましょう。するとあっというまに目的のXMLが画面に出力されました!こうしてなんと残りの5分の4がたったの半日で終わってしまいました!単純な作業は工夫して楽に楽しく素早く片付けてしまいたいですね。

そうそう、ExcelやGoogle Docsのスプレッドシートを一時作業用に使うのはおすすめです。なんだかんだで表を作るならすごく操作しやすいですし、その後作った表をテキストエディタにコピペすれば TSV が簡単に作れるので、便利です。

2010年12月29日水曜日

はじめての iPhone 開発時にお勧めの本 3 冊

2010/12/29追加: 本を追加したり中身を最新のものにしたりしました。

@monjudoh に社内チャットで
[10/09/17 17:18:12] 文 殊堂: heyあきすて
[10/09/17 17:18:32] 文 殊堂: iPhoneアプリ開発でいい本とかある?
[10/09/17 17:19:42] akisute: yoじゅどうもん
[10/09/17 17:19:43] akisute: たくさんあるぜ
[10/09/17 17:19:52] wozozo ☿: yo
[10/09/17 17:19:53] akisute: なんかみんなから同じ質問されるからブログに書くわ
とか言われたので、お勧め本をまとめてみることにしました。2年前にも似たようなものを書いたような気がしますけど、2年が経過していますから、いろいろと状況も変化していますしね。

4797361786詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ 2010-12-17

by G-Tools


2年前にも詳解しましたがこいつはバイブルです。改訂版になってBlockがらみの記述が増えたり、iOSについての言及が増えて、さらに良書になりました。四の五の言わずに買っておくと良いです。ただし、どちらかというとObjective-Cに焦点を置いている本であり、iOSで何ができるか、ということについてはほとんど記載がないので注意してください。

4797358106はじめてのiPhone3プログラミング
Dave Mark Jeff LaMarche 鮎川 不二雄
ソフトバンククリエイティブ 2009-12-17

by G-Tools


こちらは iOS のバイブルです。初めて挑戦する人に最適ですが、ある程度熟練した人が見ても発見がある良書です。およそ必要なことはすべて得られると思います。

4897978440iPhoneプログラミングUIKit詳解リファレンス
所 友太 京セラコミュニケーションシステム株式会社
リックテレコム 2010-01-12

by G-Tools


UIKitに焦点を置いて書かれている本です。図解が多く、全くiOS開発をしたことがない人でもどのようなUI要素が使えるのか見て学べるため大変おすすめです。

ASIHTTPRequest を使って外部 API から非同期的に結果を取得してみる

ASIHTTPRequest という神の通信ライブラリを使って、うまい具合に外部 API から非同期的に結果を取得・解析して返すようなクラスをつくってみました。以前 NSOperation でやってみたバージョンは こちら。


■主な機能

元々の ASIHTTPRequest にある機能はもちろんご利用いただけます。多すぎて説明し切れませんので、以下の記事を参考にしていただければと思います><

http://d.hatena.ne.jp/ninjinkun/20101122/1290394265
http://macisv.jp/blog/?p=235

さらに今回私が作成した ASIAPIRequest にはこのようなおいしい特典がつきました。
  • POST のパラメータだけではなく、 GET のパラメータも楽々生成してくれるメソッドを用意しました。
  • 非同期実行時の通知方法が, delegate, blockに加え、さらに NSNotification による通知もサポートしました。
  • 非同期的に取得したレスポンスの値をパースするためのコールバックを用意しました。このコールバックメソッドをオーバーライドして、サブクラスで処理を行えば、この中の処理はすべて非同期実行されるため、 XML のパースが遅くて UI が固まったなんてことはもうありません。
  • おまけ的にタグとかつけられるようにしてみました。
その他、お使いになられる際に適当に ASIAPIRequest の中身を書き換え御社のプロジェクトに合うように調整するなどすると面白いと思います。自動的にログインパラメータをつけるようにしたりとか。


■ダウンロード

github にリポジトリを作りましたので、こちらから git でクローンするか、または master のソースコードをダウンロードしてください。

https://github.com/akisute/asi-http-request

タグが付与されていますが、これはクローン元のタグなので、当てにしないでください。常に master の先端をダウンロードするのが一番確実です。

ダウンロードしたら、 Classes ディレクトリと External ディレクトリの中身を適当に自分のプロジェクトにコピーして、プロジェクトに追加していただければOKです。最後に、以下のフレームワークをリンクしてください。
  • CFNetwork
  • SystemConfiguration
  • MobileCoreServices
  • CoreGraphics
  • zlib
ライセンスは元のライブラリに合わせて BSD ライセンスとします。


■使い方

最初に ASIAPIRequest を継承してサブクラスを作成します。
// APIAuthorize.h

@interface APIAuthorize : ASIAPIRequest {
}

// 認証APIのインスタンスを生成する
+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password;

@end
では次にAPIインスタンスを生成するためのクラスメソッドをサブクラスの内部に作ってみましょう。
// APIAuthorize.m

+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password {
NSURL *url = [NSURL URLWithString:@"authorize.json" relativeToURL:API_BASE_URL_STRING];
APIAuthorize *api = [APIAuthorize requestWithURL:url];
api.requestMethod = @"POST";
[api setPostValue:userId forKey:@"userId"];
[api setPostValue:password forKey:@"password"];
api.postRequestFinishedNotificationName = @"APIAuthorizeDidFinishNotification"; // POST 成功時に飛ぶnotificationの名前
api.postRequestFailedNotificationName = @"APIAuthorizeDidFailNotification"; // POST 失敗時に飛ぶnotificationの名前
return api;
}
最後にサブクラス内部でスーパークラスのメソッドをオーバーライドし、通信完了直後に呼び出される処理を記述します。たとえば、レスポンスが返ってきた際に、受け取ったレスポンスをパースして DB に保存したりします。このコールバック内部は UI スレッドとは別のスレッドで並列実行されているので、この中でどれだけ重い処理をしても UI は固まりません。その代わり UI を操作する処理はここでは行わないでください。クラッシュします。
// APIAuthorize.m

- (void)postRequestFinished {
// レスポンスステータスコードが異常系の場合はなにもしない
if (self.responseStatusCode != 200) {
return;
}

// レスポンスをパースしてオブジェクトにし、Core Dataに保存する
// 保存したオブジェクトをuserInfoに格納しておく
User *user = [User managedObjectFromJsonString:[self responseString]
inContext:[AppDelegate appDelegate].managedObjectContext];
[[AppDelegate appDelegate].managedObjectContext save:nil];
self.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
user, @"user",
nil];
}

- (void)postRequestFailedWithError:(NSError *)theError {
// なにもしない
}
tag プロパティを使って、同じAPIでレスポンスの種類を分けることができたりします。
// APIAuthorize.m

enum {
APIAuthorizeResponseTagUser,
APIAuthroizeResponseTagToken,
};

+ (id)apiWithUserId:(NSString :)userId password:(NSString :)password {
NSURL *url = [NSURL URLWithString:@"authorize.json" relativeToURL:API_BASE_URL_STRING];
APIAuthorize *api = [APIAuthorize requestWithURL:url];
api.requestMethod = @"POST";
[api setPostValue:userId forKey:@"userId"];
[api setPostValue:password forKey:@"password"];
api.postRequestFinishedNotificationName = @"APIAuthorizeDidFinishNotification"; // POST 成功時に飛ぶnotificationの名前
api.postRequestFailedNotificationName = @"APIAuthorizeDidFailNotification"; // POST 失敗時に飛ぶnotificationの名前
api.tag = APIAuthorizeResponseTagUser // このAPIのレスポンスはUser型だよーとタグをつけておく
return api;
}

- (void)postRequestFinished {
switch (self.tag) {
case APIAuthorizeResponseTagUser:
// Userオブジェクトを作る
break;
case APIAuthroizeResponseTagToken:
// Tokenオブジェクトを作る
break;
default:
break;
}
}
これで API 本体は完成したので、早速実行してみましょう。以下の4つの方法で実行が可能です。
  1. 同期実行
  2. 非同期実行、 delegate で結果を通知してもらう
  3. 非同期実行、 NSBlock で通信完了後の処理を行う
  4. 非同期実行、 NSNotification で結果を通知してもらう
1, 2, 3 については普通の ASIHTTPRequest と同じですので割愛します。 4 は私が新しく追加した機能で、 NSNotification の仕組みを使って実行完了通知を受け取ることが可能になります。たとえばこんな感じになります。
// 適当に認証とかする画面のViewController.m

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Notification通知を開始する
[[NSNotificationCenter defaultCenter] addObserver:self
name:@"APIAuthorizeDidFinishNotification" // さっき決めた文字列
target:self
action:@selector(apiAuthorizeDidFinish:)
object:nil];
}

- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// Notification通知をオフにする
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (IBAction)startAuthorize {
ASIAPIRequest *api = [APIAuthorize apiWithUserId:self.userIdField.text password:self.passwordField.text];
[api startAsynchronomus];
}

- (void)apiAuthorizeDidFinish:(NSNotification *)notification {
// 認証完了時の処理
}
この通知はメインスレッドから呼び出されるので、自由に UI を操作することが可能です。

delegate と比べて、 NSNotification を使った通知の便利な点は以下の通り。
  • delegate を使う場合には、 delegate オブジェクトがメモリから消える前に delegate の始末を行う必要があるが、 NSNotification を使った場合にはその必要がない。自分を NSNotificationCenter から削除するだけでよいので、 API リクエストを比較的投げっぱなしにできる。
  • delegate とは違い、複数のオブジェクトで同時に通知を受け取ることができる。たとえば API を実行した画面とは全然違う別の画面二つで同時に通知を受け取ったりすることが可能になる。

■余談

この ASIHTTPRequest は本当にすばらしいです。最初にこの通信ライブラリを知ったときは、また良くあるただのちょっと便利なだけな通信ライブラリなんだろうと思い気にもとめなかったのですが、実際にコードを見てびっくりしました。私がほしかった通信ライブラリそのものだったからです。

私は通信ライブラリは NSOperation を継承して作るべきだと考えており、実際以前に試してみたことがありました。それは主に、
  • NSOperationQueue のシングルトンインスタンスが勝手に通信をすべて管理してくれるので、クラスの変数として通信クラスのインスタンスを保持しておかなくても良くなるかもしれない
  • NSOperation には依存関係を指定するメソッドがあるので、これを用いれば自動的に通信 A, B, C を順序通りに実行するなどできるかもしれない
  • NSOperation を継承すれば、将来 Apple の中の人がフレームワークを改善した際にマルチコア化した iPhone の CPU の恩恵を自動的に受けられるかもしれない
という考えがあったからです。そしたら見事にこの ASIHTTPRequest が NSOperation を継承して、しかも何かと問題の多い NSURLConnection を使わず NSStream とソケットを用いて自分で Run Loop を回すというすばらしい実装をしているじゃないですか!こりゃもうかないません。自分でやる必要が丸でなくなってしまいました。しかもそれだけではなく、
  • 現在全体の何%まで読み込みが完了したかを delegate で通知できる
  • 通信完了時に呼び出される delegate method はすべて main thread から呼び出されるので、 UI 操作をしても安全
という、まさにかゆいところに手が届く良さがすべてあります。惚れる。もうおそらく当分の間はこれ以上の通信ライブラリが現れることはないんじゃないかと言い切って良いぐらいすてきです。