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

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年8月15日日曜日

CALayer を使って UIImage を描画する

UIImage を高速で描画する必要がある案件に遭遇したため、 CALayer を使ってみました。 CALayer と聞くとなにやら難しい感じがしますが、実際に使ってみると非常に簡単で高速です。

CALayer を使うと良い場面は以下のような場合です。
  • 画像を大量に描画する必要がある
  • 画像を高速に描画する必要がある
  • 画像を高速に変形・移動する必要がある
  • CGContextDrawImage を今使っている箇所がある
とくに変形に対して非常に強いです。 CGContextDrawImage で変形後の UIImage を再度描画し直したりするのに比べると、 CALayer の変形は格段に高速に動作します。


■実際に描画してみる

まず最初に <QuartsCore/QuartsCore.h> をインポートします。 QuartzCore.framework をプロジェクトに追加するのも忘れないようにしましょう。

準備ができたので描画します。 CALayer.contents プロパティに CGImageRef を渡すと後は全部勝手にやってくれます。簡単でしょ?
- (void)viewDidLoad {
    // UIImage* 型のプロパティ self.image があると仮定して・・・
    CALayer *l = [CALayer layer];
    l.contents = self.image.CGImage;
    l.position = CGPointMake(255, 255) // l.position はデフォルトではレイヤー中央の座標になります
    [self.view.layer addSubLayer:l];
}
CGContextDrawRect のような Core Graphics (Quartz) の関数を使用すると、座標系が左下基準になるため、 UIImage を描画する際にコンテキストの上下を反転してやらないと画像が上下反対に表示されてしまう問題がありますが、 CALayer はこの座標系の差異も勝手に考慮に入れてくれるので、 UIImage の CGImage プロパティをそのまま渡すだけでよく、ラクチンです。


■アニメーションしてみる

もともと CALayer は Core Animation フレームワークのクラスですから、当然アニメーションにも対応しています。といいますか、何もしないでそのまま CALayer のプロパティを変えると勝手にアニメーションします。 http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/AnimatingLayers.html の Animation > Implicit Animation あたりに記載されている内容がそれです。
// 既にこの l が addSubLayer されている場合、
// これだけで勝手にアニメーションします
l.transform = CGAffineTransformCreateScale(1.1, 1.1);
が、ときどきこのアニメーションが邪魔になる場合があります。そんなときは
// CATransaction というクラスを使用して, 一時的にレイヤーのアニメーションを切ります
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
// CATransaction というクラスを使用して, 一時的にレイヤーのアニメーションの時間を変化させます
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
                 forKey:kCATransactionAnimationDuration];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];
こんな具合でアニメーションを調整できます。


■まとめ

たったのこれだけで画面上に高速でアニメーションもできる画像を描画することができます。お絵かきソフトなどで、画面上にユーザーが任意の画像を挿入できるようにしたい、と言ったときに大変役立ちますのでおすすめです。是非試してみてください。