ラベル Three20 の投稿を表示しています。 すべての投稿を表示
ラベル Three20 の投稿を表示しています。 すべての投稿を表示

2011年3月17日木曜日

Xcode 4 で昔の Three20 フレームワークを使っているプロジェクトをビルドする方法

主に自分用のメモ。
Xcode 3 時代に作った Three20 フレームワークを使っているプロジェクトが動かない場合の対処法です。


動かない原因は簡単で、Xcode 4からはビルドした生成物が用意される場所が、各プロジェクトのbuildディレクトリ以下から、
~/Library/Developer/Xcode/DerivedData/プロジェクト名/Build/Products
以下に移動しているからです。なので、たいていの場合ヘッダ検索パスが間違っているので動かなくなります。

対処法はThree20公式ブログに載っているので転載します。
http://three20.info/article/2011-03-10-Xcode4-Support

以下のようにヘッダ検索パスを追記すると良いようです。
"$(BUILT_PRODUCTS_DIR)/../three20"
"$(BUILT_PRODUCTS_DIR)/../../three20"


2011/04/03追記: はじめに載せていた対処法は間違いでしたorz 大変申し訳ありません!
以下、はじめに掲載していた間違っている対処法です。この方法では通常のビルドはうまくいきますが、アーカイブ時のビルドに失敗します!どうもアーカイブ時と通常のビルド時でもビルド先のディレクトリが指し示す先が異なるようです、

対処法は、以下のようにヘッダ検索パスを書き直します。
$SYMROOT/three20


$SYMROOTという環境変数がキモです。



こいつはドキュメントには書いてませんが、Xcode 4のビルド設定ですと、
~/Library/Developer/Xcode/DerivedData/プロジェクト名/Build/Products
に対応するパスを返してくれるようです。

2011/04/03追記: 上にも書きましたが、アーカイブ時には上記に対応するパスではなく、異なるパスが使われます。そのためこの方法ではうまくいきません。Three20.infoに掲載されている方法を使うのが一番良いようです。

2010年9月5日日曜日

Three20 の TTURLRequest は POST メソッドのリクエストもキャッシュしてしまう

Three20 には TTURLRequest という Three20 フレームワークの通信関連を一手に引き受けている通信用クラスがあります。基本的には NSURLConnection クラスとほぼ同等なのですが、リクエストを作ったりレスポンスを delegate でハンドルするのがより簡単になるように作られていたり、独自のキャッシュを使用してより効率的なキャッシュをするようになっていたり、様々な点で NSURLConnection より優れていて便利に使えます。

ですがいくつかハマリどころもありまして、今回はそれを紹介します。ちなみに今回使用した Three20 のバージョンは http://github.com/facebook/three20 の default ブランチの 2010/09/05 付での最新コミットです。今後修正される可能性があります。

実は TTURLRequest は、デフォルトでは HTTP メソッドの種類に関係なく、一律すべてのURLをキャッシュするようになってしまっています。そのため、キャッシュ設定をせずに POST メソッドを使って Web API を実行したり、 RESTful な Webアプリ に PUT や DELETE を送ってしまうと、二回目以降のリクエストがキャッシュされてしまいサーバーにリクエストが飛ばなくなってしまいます。回避方法は以下のどちらかを使うと良いです。
  1. 手動でリクエストを作成する際にキャッシュ設定を明示的に指定する
  2. フレームワーク側を書き換えてしまい、 POST, PUT, DELETE 実行時にはキャッシュを無視するようにする

■手動でリクエストを作成する際にキャッシュ設定を明示的に指定する
一番簡単です。以下のようにして明示的にキャッシュを使わないように指定します。
TTURLRequest *request = [TTURLRequest requestWithURL:@"http://mypage.example.com/api/something/post" delegate:self];
request.httpMethod = @"POST";
request.cachePolicy = TTURLRequestCachePolicyNone;

■フレームワーク側を書き換える
でも個人的には GET 以外でリクエスト結果がキャッシュされるのは誰がなんと言おうと不具合だと思っているので、 TTURLRequestQueue.mloadRequestFromCache: メソッドを以下のように書き換えて対応しました。
- (BOOL)loadRequestFromCache:(TTURLRequest*)request {
if (!request.cacheKey) {
request.cacheKey = [[TTURLCache sharedCache] keyForURL:request.urlPath];
}

if (IS_MASK_SET(request.cachePolicy, TTURLRequestCachePolicyEtag)) {
// Etags always make the request. The request headers will then include the etag.
// - If there is new data, server returns 200 with data.
// - Otherwise, returns a 304, with empty request body.
return NO;
//-----------------------------------------------------------------------
// ここから下が変更点
//-----------------------------------------------------------------------
} else if ([request.httpMethod isEqualToString:@"POST"] || [request.httpMethod isEqualToString:@"PUT"] || [request.httpMethod isEqualToString:@"DELETE"]) {
// HTTP POST/PUT/DELETE should not use cache.
// Only HTTP GET can use this cache.
return NO;
//-----------------------------------------------------------------------
// ここまでが変更点
//-----------------------------------------------------------------------
} else if (request.cachePolicy & (TTURLRequestCachePolicyDisk|TTURLRequestCachePolicyMemory)) {
これですべてのHTTPリクエストにおいて、 POST, PUT, DELETE 時にキャッシュが使われなくなります。

2010年4月29日木曜日

Three20 の TTPhotoViewController でローカルファイルを扱う際の注意点

Three20 フレームワークを使ってアプリを作成しているときに、ローカルファイルを TTPhotoViewController に読み込ませようとして一部はまってしまったので共有します。


■結論から
  • TTPhotoViewController の中に一部 NSHTTPURLResponse でないと動かないところがあるため、ファイル読み込みでは動作しない
  • @"bundle://filename.jpg"@"documents://filename.jpg" といった記法を用いることで回避可能
  • またはフレームワーク自体を書き換えてしまう

■問題
以下のコードの様に、ファイルスキームのURLを用いて画像を読み込もうとするとエラーになります。
photos = [[NSMutableArray alloc] init];
PhotoBookPhoto *photo;
NSString *imagesDirectoryPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Images"];
NSArray *imagePathes = [[NSFileManager defaultManager] directoryContentsAtPath:imagesDirectoryPath];

for (int i=0; i<imagePathes.count; i++) {
NSString *imagePath = (NSString *)[imagePathes objectAtIndex:i];
imagePath = [imagesDirectoryPath stringByAppendingPathComponent:imagePath];
LOG(@"%d - %@ %@", i, imagePath, [[NSURL fileURLWithPath:imagePath] absoluteString]);
photo = [[[PhotoBookPhoto alloc] initWithURL:[[NSURL fileURLWithPath:imagePath] absoluteString]
smallURL:[[NSURL fileURLWithPath:imagePath] absoluteString]
size:CGSizeMake(960, 640)] autorelease];
photo.photoSource = self;
photo.index = i;
[photos addObject:photo];
}

■原因
TTRequestLoader 中の以下のコードが直接的な原因です。
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSURLConnectionDelegate


///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)connection:(NSHTTPURLConnection*)connection didReceiveResponse:(NSHTTPURLResponce*)response {
_response = [response retain];
NSDictionary* headers = [_response allHeaderFields];
本来 NSURLConnectionDelegate のデリゲートメソッドである、 connection:didReceiveResponse:NSURLResponce を引数にとるのですが、何故か NSHTTPURLResponce が直接指定されています。通常はこれでも問題ありませんが、fileスキームのURLは当然HTTPではありませんから、 NSHTTPURLResponse を返しません。従って allHeaderFields の呼び出しでエラーになってしまいます。


■対処法
ということで、この部分のコードを一部書き換えてしまい、 NSURLResponce がきても動作する様にしてしまいました。
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSURLConnectionDelegate


///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
_response = [response retain];
if ([_response isMemberOfClass:NSHTTPURLResponse.class]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)_response;
NSDictionary* headers = [httpResponse allHeaderFields];
int contentLength = [[headers objectForKey:@"Content-Length"] intValue];

// If you hit this assertion it's because a massive file is about to be downloaded.
// If you're sure you want to do this, add the following line to your app delegate startup
// method. Setting the max content length to zero allows anything to go through. If you just
// want to raise the limit, set it to any positive byte size.
// [[TTURLRequestQueue mainQueue] setMaxContentLength:0]
TTDASSERT(0 == _queue.maxContentLength || contentLength <=_queue.maxContentLength);

if (contentLength > _queue.maxContentLength && _queue.maxContentLength) {
TTDCONDITIONLOG(TTDFLAG_URLREQUEST, @"MAX CONTENT LENGTH EXCEEDED (%d) %@",
contentLength, _urlPath);
[self cancel];
}

_responseData = [[NSMutableData alloc] initWithCapacity:contentLength];
} else {
_responseData = [[NSMutableData alloc] initWithCapacity:response.expectedContentLength];
}
}
また、あとから知ったのですが、Three20においては以下のようなURLが使用できるみたいです。 Three20/TTGlobalCorePaths.h にて定義されています。
photo = [[[PhotoBookPhoto alloc] initWithURL:@"bundle://image.png"
smallURL:@"documents://thumbnails/smallImage.png"
size:CGSizeMake(960, 640)] autorelease];
たぶん Three20以外では使用できない記法 なので注意が必要ですが、この方法で読み込むと NSHTTPURLResponce が返却されるためコードを書き換えなくても正常に動作します。 こちらのほうが簡単でThree20の開発チームの人もそうしているのでお勧めです。


■んーでも
そもそもこのThree20、開発者の人(FaceBookアプリを作った人)がiPhoneでのプログラミングを既にやめちゃっていると聞いたのでメンテがどうなるのか不安です。