ページ

2010年3月31日水曜日

TBXML を試す

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: CocoaでXML

TBXML を使うサンプルを作ってみた。

ソースは GitHub からどうぞ。
TBXMLSample at 2010-03-31 from xcatsan's SampleCode - GitHub

以下、サンプルを作る手順。

まず TBXML をダウンロードする。
TBXML V1.3 - The very fast, light-weight XML parser for Apple iPad, iPhone & iPod Touch


今回は TBXML-v1.3.zip をダウンロードした。解凍すると4つのファイルが現れる。

Xcodeでプロジェクト(TBXMLSample)を作った後、この4ファイルをコピーする。
最後に Frameworks へ libz.dylib を追加すればこれで準備 OK。

試しに Twitpic の APIページに掲載されていたレスポンスのサンプルを sample.xml と error.xml としてリソースへ置いておいた。
sample.xml


error.xml

サンプルアプリは2つボタンを持っていて、それぞれボタンを押すと sample.xml と error.xml をパースして結果をコンソールへ出力する。
コードはこんな感じ。まずボタンのハンドラ。

- (IBAction)parse1:(id)sender
{
[self parseWithXMLFile:@"sample.xml"];
}

- (IBAction)parse2:(id)sender
{
[self parseWithXMLFile:@"error.xml"];
}


続いて実際のパース処理。

- (void)parseWithXMLFile:(NSString*)filename
{
TBXML* tbxml = [TBXML tbxmlWithXMLFile:filename];

TBXMLElement *element = tbxml.rootXMLElement;
NSMutableDictionary* elements = [NSMutableDictionary dictionary];

if (element) {
NSString* status = [TBXML valueOfAttributeNamed:@"status"
forElement:element];

if (status) {
if ([status isEqualToString:@"ok"]) {
element = element->firstChild;

do {
NSString* key = [TBXML elementName:element];
NSString* object = [TBXML textForElement:element];
[elements setObject:object forKey:key];

} while (element = element->nextSibling);

NSLog(@"ok: %@", elements);

} else {
// fail
element = element->firstChild;
if (element) {
NSString* errorCode = [TBXML valueOfAttributeNamed:@"code"
forElement:element];
NSString* errorMsg = [TBXML valueOfAttributeNamed:@"msg"
forElement:element];
NSLog(@"fail: code=%@, msg=%@", errorCode, errorMsg);
}
}
} else {
// error
}
}
}


基本的に TBXML のクラスメソッドを使う。データはクラスではなく構造体として定義されているので、純粋なCプログラミングに近い。


実行してみよう。まず sample.xml のパース。

TBXMLSample[1416:80f] ok: {
    mediaid = abc123;
    mediaurl = "http://twitpic.com/abc123";
    statusid = 1111;
    userid = 11111;
}

出た。

続いて error.xml のパース。

TBXMLSample[1476:80f] fail: code=1001, msg=Invalid twitter username or password

出た。

- - - -
クラスメソッドと構造体を使うのが特徴的。インスタンスを作らないのはフットプリントを減らす為だと思われる。Objective-C というよりは Cプログラミングに近いが、慣れれば比較的簡単にコードが書ける。

2010年3月30日火曜日

CocoaでXML

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Cocoa で XMLを使う方法を少し調べてみた。

NSXMLParser

NSXMLParser の使い方
Mac Dev Center: Event-Driven XML Programming Guide for Cocoa: Introduction to Event-Driven XML Programming Guide for Cocoa

リファレンス
Mac Dev Center: NSXMLParser Class Reference




XPathQuery

libxml2 のCocoaラッパー。

Cocoa with Love: Using libxml2 for XML parsing and XPath queries in Cocoa

XPathQueryを使ったXMLのパース - Tomute’s Notes


その他

libxml2 の方が高速
【コラム】実践! iPhoneアプリ開発 (7) RSSリーダの作り方 (3) - XMLをパースする | エンタープライズ | マイコミジャーナル

以前XMLパーサライブラリの比較記事を紹介したことがある。
Cocoaの日々: 今週のCocoa情報(3/21) - 今週気になった Cocoaプログラミング情報の紹介

その中からリンクを集めてみた。

- - -
Twitpic のレスポンスが XMLなのでその解析で使うライブラリを探している。Twitpic のXMLは簡単なのでどれでもいいのだが、今後他のアップローダを作る時にも同じライブラリを使いたい。さて、どれを使うか。ポイントは、パフォーマンス良く、メモリ少なく、使うのが簡単の3つ。

TBXML は "How To Chose The Best XML Parser for Your iPhone Project" でもパフォーマンスが好成績。メモリの利用量も一番少ない。ドキュメントが比較的整っているのもいい。


日本語の紹介記事のあった XPathQuery も気になる。サンプルを見る限りでは使うのが簡単そう。




TBXML を試してみることにする。

2010年3月29日月曜日

Keychain Services 調査 (23) twitpic へ画像をアップロード(その3)画像をアップロード

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (22) twitpic へ画像をアップロード(その2)asi-http-request を使う

いよいよ twitpic へ画像をアップロードする。今回は sample.jpg を用意してこれをアップロードしてみよう。


asi-http-request ではフォームデータ送信用のクラス ASIFormDataRequest が用意されており、これを使うと multipart/form-data なリクエストも数行で送信することができる。使い方は How to use it の中の Sending data with POST or PUT requests に書かれている。

ASIHTTPRequest example code - All-Seeing Interactive

ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; 
[request setPostValue:@"Ben" forKey:@"first_name"]; 
[request setPostValue:@"Copsey" forKey:@"last_name"]; 
[request setFile:@"/Users/ben/Desktop/ben.jpg" forKey:@"photo"];

やってみよう。sample.jpg を xcodeのプロジェクトへ追加した後、下のコードを書く。

NSURL *url = [NSURL URLWithString:@"http://twitpic.com/api/uploadAndPost"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];

[request setPostValue:loginAccount.loginId forKey:@"username"];
[request setPostValue:loginAccount.password forKey:@"password"];

NSString* filepath = [[NSBundle mainBundle] pathForImageResource:@"sample"];
NSLog(@"filepath=%@", filepath);

[request setFile:filepath forKey:@"media"];
[request setPostValue:@"Hello twitpic!" forKey:@"message"];
[request startSynchronous];

NSError* error = [request error];
if (!error) {
NSString* response = [request responseString];
NSLog(@"%@", response);
} else {
NSLog(@"error: %@", error);
}


実行してみよう。ID, パスワードを入力する。
Login を押すとリクエストが飛ぶ。ログを見るとうまく行っているようだ。

KeychainSample2[3461:80f] updated keychain item
KeychainSample2[3461:80f] filepath=/Users/hashi/development/SampleCode/KeychainSample2/build/Debug/KeychainSample2.app/Contents/Resources/sample.jpg
KeychainSample2[3461:80f]
 111...
 1254....
 1bh2pu
 http://twitpic.com/1bh2pu



Twitpic のページを表示してみる。

出た。ちょっとうれしい。

2010年3月28日日曜日

今週のCocoa情報(3/28) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ASIHTTPRequest Documentation - All-Seeing Interactive

HTTP 通信用ライブラリ。iPhone開発では有名? これはかなりよく出来ている。現在 Twitpic クライアントの開発に利用している。


Using #error and #warning Compiler Directives

#error や #warning の使い方。

#error をこんな風に書いておくとビルド時にエラー(Build Failed)となる。


#error : Change this value to your Flickr key
NSString *const FlickrAPIKey = @"your-key-here";
変更し忘れをビルド時に指摘することができる。これは便利。

#warnng はエラーにはならないがコンソールにメッセージが表示される。


inessential.com: On switching away from Core Data

iPhone アプリ NetNewsWire の作者が、CoreData から 直接 SQLite使う方法(FMDBを利用)へ切り替えたことについて語る。


Mac Dev Center: Services Implementation Guide: Introduction

Mac Dev Center よりサービスのリファレンス。サービスとはアプリメニューに現れるこんなやつ。

このサービスを活用できないかと調査中。

2010年3月27日土曜日

Keychain Services 調査 (22) twitpic へ画像をアップロード(その2)asi-http-request を使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (21) twitpic へ画像をアップロード(その1)調査

asi-http-request ライブラリを使うことにする。まずはサンプルプログラムへライブラリを組み込んで動作確認をやってみる。

組み込み方法はサポートページに図入りで詳しく書いてある。
How to use ASIHTTPRequest in your projects - All-Seeing Interactive


まずライブラリのソースコードをダウンロードする。
Downloads for pokeb's asi-http-request - GitHub

今回は v1.6.1 の zip を落としてみた。解凍すると Xcodeのプロジェクト一式が入っている。

Classes 内のファイル(11個)をサンプルプログラムのプロジェクトへコピーする。
次にサンプルプログラムのプロジェクトへフレームワークと動的ライブラリを追加する。追加するのは次の2つ。



Mac OS X の場合はこれで終わり。iPhone の場合はもういくつかのファイルを追加擦る必要がある。


動作確認で google.com のページを GET してみよう。サポートページの "How to use it" を参考に同期リクエストを投げてみる。
ASIHTTPRequest example code - All-Seeing Interactive


NSURL *url = [NSURL URLWithString:@"http://www.google.com/"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError* error = [request error];
if (!error) {
NSString* response = [request responseString];
NSLog(@"%@", response);
} else {
NSLog(@"error: %@", error);
}

実行結果



(続く)

2010年3月26日金曜日

Keychain Services 調査 (21) twitpic へ画像をアップロード(その1)調査

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (20) 認証フロー(REST向け)Keychain item 更新

さて今回からこれまで作ってきた keychain servicesを使った認証実装を使い、 twitpic へ画像をアップロードするサンプルアプリを作ってみる。

まずは調査から。

Twitpic - Share photos on Twitter

twitpic では簡単な API が用意されていて、POSTで画像をアップロードすると XMLでレスポンスが返ってくる。アップロードには multipart/form-data を使う。


METHOD: http://twitpic.com/api/uploadAndPost


Use this method to upload an image to TwitPic and to send it as a status update to Twitter.


Fields to post in
(post data should be formatted as multipart/form-data):
- media (required) - Binary image data
- username (required) - Twitter username
- password (required) - Twitter password
- message (optional) - Message to post to twitter. The URL of the image is automatically added.


Sample response:

 1111
 11111
 abc123
 http://twitpic.com/abc123
このAPIを使うと twitter への投稿も一緒にやってくれる。なお http だと username / password が盗聴/改ざんされる危険があるのが気になる。


Objective-C の実装についてはいくつか情報が見つかった。

iPhoneアプリからTwitpicに画像をアップロードする方法 - Tomute’s Notes

Using TwitPic API from ObjectiveC/iPhone - Stack Overflow

どちらも ASIHTTPRequest というライブラリを使っているようだ。

pokeb's asi-http-request at master - GitHub

READMEを引用すると:
ASIHTTPRequest is an easy to use wrapper around the CFNetwork API that makes some of the more tedious aspects of communicating with web servers easier. It is written in Objective-C and works in both Mac OS X and iPhone applications.
It is suitable performing basic HTTP requests and interacting with REST-based services (GET / POST / PUT / DELETE). The included ASIFormDataRequest subclass makes it easy to submit POST data and files using multipart/form-data.

今回 multipart/form-data で POST するので、このライブラリを使うのが便利そうだ。

(続く)

2010年3月25日木曜日

Keychain Services 調査 (20) 認証フロー(REST向け)Keychain item 更新

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (19) 認証フロー(REST向け)実装開始

以前から Keychain item の作成とパスワードの取得は実装していたが、更新はまだだった。今回更新処理を実装する。

更新には SecKeychainItemModifyAttributesAndData を使う。
Mac Dev Center: Keychain Services Reference - SecKeychainItemModifyAttributesAndData

-[AccountManager storeLoginAccount:] にこの関数を使った更新処理を加える。

- (BOOL)storeLoginAccount:(LoginAccount*)loginAccount
{
OSStatus status;
SecKeychainItemRef itemRef = nil;
BOOL result = NO;

const char *serviceNameUTF8 = [loginAccount.serviceName UTF8String];
const char *loginIdUTF8 = [loginAccount.loginId UTF8String];
const char *passwordUTF8 = [loginAccount.password UTF8String];

status = SecKeychainFindGenericPassword(NULL,
strlen(serviceNameUTF8),
serviceNameUTF8,
strlen(loginIdUTF8),
loginIdUTF8,
NULL,
NULL,
&itemRef);
if (status == errSecItemNotFound) {
status = SecKeychainAddGenericPassword(NULL,
   strlen(serviceNameUTF8),
   serviceNameUTF8,
   strlen(loginIdUTF8),
   loginIdUTF8,
   strlen(passwordUTF8),
   passwordUTF8,
   NULL);
if (status == errSecSuccess) {
NSLog(@"created keychain item");
result = YES;
} else {
NSLog(@"ERROR:SecKeychainAddGenericPassword:%d", status);
result = NO;
}

} else {
status = SecKeychainItemModifyAttributesAndData(itemRef,
NULL,
strlen(passwordUTF8),
passwordUTF8);
if (status == errSecSuccess) {
NSLog(@"updated keychain item");
result = YES;
} else {
NSLog(@"ERROR:SecKeychainItemModifyAttributesAndData:%d", status);
result = NO;
}
}
if (itemRef) {
CFRelease(itemRef);
}

return result;
}


最初に keychain を検索し、無ければ SecKeychainAddGenericPassword で新規作成する。存在する場合は SecKeychainItemModifyAttributesAndData を使い更新する。


ソースは GitHub からどうぞ。
KeychainSample2 at 2010-03-25 from xcatsan's SampleCode - GitHub


なお、前回 NSTextField にバインディングされたメンバ変数の同期が遅れていた件は、Bindings設定で "Continuously Updates Value" を設定することで解決した。


- - - -
次回こそは Twitpic にとりかかりたい。

2010年3月24日水曜日

Keychain Services 調査 (19) 認証フロー(REST向け)実装開始

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (18) 認証フロー(REST向け)

以前のコードを前回の認証フロー向けに書き直す。新たにプロジェクトを作り、前回までのコードで必要なものをコピーする。

最初にアカウント情報(サービス、ID、パスワード)をまとめて扱えるようにモデルクラスを1つ作る。
LoginAccount.h

@interface LoginAccount : NSObject {

NSString* serviceName;
NSString* loginId;
NSString* password;
}

@property (copy) NSString* serviceName;
@property (copy) NSString* loginId;
@property (copy) NSString* password;

@end


KeychainManager は AccountManager と改名し、LoginAccount を扱うメソッドに変更する。このクラスで KeychainManager からパスワードを取得したり、保存を行ったりする。
AccountManager.h

@class LoginAccount;
@interface AccountManager : NSObject {

}

+ (AccountManager*)sharedManager;
-(BOOL)setPasswordWithLoginAccount:(LoginAccount*)loginAccount;

@end


認証シートのコントローラ AuthenticationWindowController は新しい処理フロー図に合わせて書き直す。

AuthenticationWindowController.h

@class LoginAccount;
@interface AuthenticationWindowController : NSObject {

IBOutlet NSWindow* window_;
IBOutlet NSTextField* loginIdTextField_;
IBOutlet NSSecureTextField* passwordTextField_;

NSWindow* attachedWindow_;
NSString* loginId_;
NSString* password_;

NSString* message_;
BOOL is_canceled_;

}

@property (assign) NSWindow* attachedWindow;
@property (copy) NSString* loginId;
@property (copy) NSString* password;

@property (retain) NSString* message;

-(BOOL)storeLoginAccount:(LoginAccount*)loginAccount;

-(IBAction)login:(id)sender;
-(IBAction)cancel:(id)cancel;
@end


クライアントコードは、-[storeLoginAccount:] を使いユーザからID,パスワードを取得する。ここでの処理がフロー図の右側の点線内の処理に当たる。

今回シートウィンドウはコールバックを使わず、表示中は実行がそこで止まる(ブロック)するようにした。これはデリゲートに nil を指定すると簡単にできる。

(参照)Mac Dev Center: Sheet Programming Topics for Cocoa: Using Application-Modal Dialogs

コードはこんな感じ。

-(BOOL)storeLoginAccount:(LoginAccount*)loginAccount
{
if (loginAccount.loginId) {
self.loginId = loginAccount.loginId;
if (!loginAccount.password) {
[[AccountManager sharedManager]
setPasswordWithLoginAccount:loginAccount];
}
}
[window_ makeFirstResponder:loginIdTextField_];
self.message = @"";

[NSApp beginSheet:window_
  modalForWindow:attachedWindow_
modalDelegate:nil
  didEndSelector:nil
  contextInfo:nil];
[NSApp runModalForWindow:window_];
// dialog is up here (wait for closing)

[NSApp endSheet:window_];
[window_ orderOut:nil];
if (!is_canceled_) {
loginAccount.loginId = self.loginId;
loginAccount.password = self.password;
return YES;

} else {
return NO;
}
}

最後に呼び出し側(クライアント側)のコード。
KeychainSample2AppDelegate.m
-(IBAction)connect:(id)sender
{
LoginAccount* loginAccount = [[[LoginAccount alloc] init] autorelease];

loginAccount.loginId =
[[NSUserDefaults standardUserDefaults] valueForKey:@"loginId"];
loginAccount.serviceName = SERVICE_NAME;

if ([authenticationWindowController_ storeLoginAccount:loginAccount]) {
// TODO: send request
NSLog(@"%@", loginAccount);
} else {
// cancel
NSLog(@"cancend");
}
}


実行してみよう。最初にボタン一つのウィンドウが現れる。

ボタンを押すとID,パスワードを求められる。

login または cancel を押すとシートが閉じられる。それだけ。


今回IDとパスワードが空の場合にエラーメッセージを表示するようにした。
あらかじ NSTextField を貼ってメンバ変数へバインドしておき、エラー時にその変数へエラーメッセージを代入している。

-(IBAction)login:(id)sender
{
NSString* loginId = [loginIdTextField_ stringValue];
NSString* password = [passwordTextField_ stringValue];
if (!loginId  || [loginId length] == 0) {
self.message = @"Username is empty";
[window_ makeFirstResponder:loginIdTextField_];
return;
}
if (!password || [password length] == 0) {
self.message = @"Password is empty";
[window_ makeFirstResponder:passwordTextField_];
return;
}

is_canceled_ = NO;
[NSApp stopModal];
}
当初は ID とパスワードの空チェックにバインドしてあるメンバ変数 self.loginId などを使っていたが、入力直後に loginボタンを押すと、バインドによる同期が間に合わず nil となってしまっていた。この為,直接コントロール(NSTextFiled)をチェックするようにしてある。



ところで認証シートを開いている時に ESCキーを押すと、cancelボタンが一瞬押された状態となり、シートが閉じられた。動作としてはいいのだが、こちらでは何も設定を行っていない。cancelという文字列をみつけて自動的にこのような動作を行っているのだろうか?名前を変えてみたり、ボタンを1つ追加してみたりとしたが、やはり自動的に認識されている。謎だ。

ソースは GitHub からどうぞ。
KeychainSample2 at 2010-03-24 from xcatsan's SampleCode - GitHub

- - - -
今回はパスワードの保存まで手が回らず。次回は Twitpic へのアクセスまで行きたい。

2010年3月23日火曜日

Keychain Services 調査 (18) 認証フロー(REST向け)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: Keychain Services 調査 (17) コーディング #5 認証フロー実装(認証ウィンドウの Nib化)

Twitpic のような1回のリクエストで認証と処理を実行するような処理では、前回までの認証フローは使いずらい。

Twitpic API
Twitpic - Share photos on Twitter


Fields to post in
(post data should be formatted as multipart/form-data):
- media (required) - Binary image data
- username (required) - Twitter username
- password (required) - Twitter password
- message (optional) - Message to post to twitter. The URL of the image is automatically added.

前回までの認証フロー図:


この場合、ライブラリ側で認証フローの制御を行うのではなく、利用側でフロー制御ができた方がいい。ライブラリ側はID/パスワードの取得だけに専念させる方が実装がしやすい。認証フロー図を書き直してみた。



パスワードは認証の成功失敗に関わらず保存している。これは Safari や FireFox と同じ(これらのブラウザでパスワードを間違えて何度か保存し直した経験があると思うがそれと同じ)。

- - - -
試しに Twitpic への簡易アップローダを作ってみよう。
(続く)

2010年3月22日月曜日

SimpleCap 1.2.1 リリース

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

SimpleCap 1.2.1 をリリースしました。

今回機能追加はありません。

ダウンロードはこちら。
SimpleCap ダウンロード


以下、変更点。

1. 下の4ヶ国語に新たに対応しました。

  • 中国語(繁体字)
  • イタリア語
  • フランス語
  • ポルトガル語


2. アイコンを変更しました。

改善前
改善後
ずいぶん垢抜けた感じになった。

アイコン改善は edm2 さん提案によるものです。edm2 さん、提案ありがとうございました。


2010年3月21日日曜日

今週のCocoa情報(3/21) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Should I always release self for failed init methods? - Stack Overflow

initメソッド内でエラーが起きた時の対処。self を release して nil を返すのが良いとのこと。



- (id)init{
   self = [super init];
   if(self) {
       // do some init stuff

       if (somethingFailed)
       {
          [self release]
          self = nil;
       }
   }

   return self;
}
Mac Dev Center にその解説がある。


Handling Initialization Failure

In general, if there is a problem during an initialization method, you should call [self release] and return nil.
There are two main consequences of this policy:


  • Any object (whether your own class, a subclass, or an external caller) that receives a nil from an initializer method should be able to deal with it. In the unlikely case where the caller has established any external references to the object before the call, this includes undoing any connections.


  • You must make sure that dealloc methods are safe in presence of partially-initialized objects.



Getting the time elapsed (Objective-c) - Stack Overflow

経過時間の計測



NSDate *start = [NSDate date];
// do stuff...
NSTimeInterval timeInterval = [start timeIntervalSinceNow];
シンプルでいい。

How To Chose The Best XML Parser for Your iPhone Project

iPhone情報だが、XML Parser の比較記事。NSXML, libxml2 をはじめ、TBXML, TouchXML, KissXML など多くのパーサーについての比較がある。XMLPerformance Test App なるものがあるのか。
グラフを見ると速度とメモリ利用率で TBXML というパーサーが良さそう。
最後に用途別にどのパーサーを選ぶと良いかが掲載されている。

read small XML documents => TouchXML, KissXML, GDataXML
read and write samll XML documets => KissXML, GDataXML
read extremely large XML documents => libxml2 SAX, TBXML, libxml DOM


QuickDrawはどのように素早く円を描いていたのか? - ザリガニが見ていた...。

円を描くアルゴリズムの解説あり。面白い。