武之新

3月 20

アプリの申請でハマる

はじめてiPhoneアプリをAppleに申請してみました。
が、そこで思いっきりハマりまくりでした。

ハマったのは次の2点。

どハマりしたのはスクリーンショットで、アップロードしたのに状態が変わらず焦りました。こんな状態になるのはユニバーサルかつLocalizeしたときくらいでしょうからレアケースだと思いますが。

3月 04

xibのローカライズでハマる

設定画面など、あちこちに文字列を埋め込んだ画面はいちいちコードでNSLocalizedStringするのが面倒なので、xibをローカライズしてしまうのが簡単だと思います。

はじめてxibをローカライズしてみたのですが、色々ハマりました。

それにしても、Xcodeはメジャーバージョンが変わるたびに作法がガラリと変わるので、ネットで調べてもどれがいまも使える方法なのかがわかりづらいですね。

3月 03

UIWebViewでフォントサイズをいじる

相変わらずUIWebViewと格闘中です。

AppStoreにあるブラウザアプリで、フォントサイズが変更できるものがあったので、真似してみることに。

やり方は簡単、次のようにやるだけ。

script = @"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='120%';";

[webView stringByEvaluatingJavaScriptFromString:script];

jQueryなどでスタイルのfont-sizeをいじっても、HTML上のスタイル定義のされ方によって適用されたりされなかったり安定しません。webkitTextSizeAdjustを使えばそういうときでもイチコロです。

元ネタはこちら。
http://stackoverflow.com/questions/589177/how-to-increase-font-size-in-uiwebview 

2月 21

続:NSURLCacheでGoogleのトップページをキャッシュすると落ちる件

結局解決できていません、本件。

あれからさらにいろいろ試して、見えてきたのは[NSURLRequest cachePolicy]。

そもそもは、デフォルトのNSURLCache実装がstoreCachedResponseでデータを渡されていてもcachedResponseForRequestで返すものと返さないものが存在している、というところでした。

なぜか?
データは違えども、NSCachedURLResponse、NSURLResponseの状態は概ね同じ。違うのは[NSURLRequest cachePolicy]。デフォルトのNSURLCacheはNSURLRequestReturnCacheDataElseLoad、NSURLRequestReturnCacheDataDontLoadの場合はキャッシュを返すけど、NSURLRequestUseProtocolCachePolicyの場合は返さないようです。

相変わらず理由はわかりませんが、真似してみることに。

ふむ、落ちなくなった

前回も落ちなくなった後、他の箇所をいじっていたら再発したので安心できませんが、とりあえず改善。どうやらものすごく微妙なバランスで動いているようです、NSURLCache。 

ただ、割といろんなリソースがNSURLRequestUseProtocolCachePolicyでリクエストされているので、NSURLRequestUseProtocolCachePolicyだとキャッシュを返しちゃだめとなるとローカル保存からWebページを再現するのは難しいのかもしれません。。

[iPhone]UIWebViewでNew Windowをなんとか検知する

先日(どころか実際には昨年から)ハマってるUIWebViewですが、懸案課題のNew Windowの検知が次のような姑息なアイデアでなんとかなりました!

やり方は次の2つの組み合わせ。

  1. UIWebViewの webView:willClickElement: をoverrideして、elementからtarget属性を探す。(Aタグ用)
  2. UIWebViewの stringByEvaluatingJavaScriptFromString: でJavascriptの”window.open”を書き換えてしまい、window.openされたら特殊なURLに飛ぶようにして、webView:shouldStartLoadWithRequest:navigationType: で検知する。(Javascript用)

(1)は簡単で、次のような感じ。

@implementation UIWebView(hack)

- (void)webView:(UIWebView *)sender willClickElement:(id)element {
    BOOL is_new_window = NO;
    NSString* nodeName = [element nodeName];
    NSString* hrefURL = [element hrefURL];
    NSString* textContent = [element textContent];
    id attributes = [element attributes];
    int length = [attributes length];
    for(int i = 0; i < length; i++ ) {
        id item = [attributes item:i];
        NSString* name = [item name];
        NSString* value = [item value];
        
        if (NSOrderedSame == [name compare:@”target” options:NSCaseInsensitiveSearch]){
            is_new_window = YES;
            break;
        }
    }
    
    if (is_new_window){
        NSLog(@”clicked!!”);
    }
}

@end

これは http://son-son.sakura.ne.jp/?p=752 を参考にさせてもらいました。

(2)はなかなか面倒で、次の2ステップが必要。
まず、webViewDidFinishLoad: でwindow.openを書き換える。書き換え方は

[webView stringByEvaluatingJavaScriptFromString:
 @”window.open = function(url, window_name, options){ 
    window_name = window_name == ” ? ‘_blank’ : window_name;
    location.href = ‘wopen://’ + (window_name == null ? ‘_blank’ : window_name) + ‘/’ + url; 
    };”];

こんな感じ。あとでURLからwindow.openが呼ばれたことと、遷移したいURLがわからなければならないので、schemeを独自のものに書き換えてしまうのがよいと思う。

続いて、webView:shouldStartLoadWithRequest:navigationType: で検知する部分。

- (BOOL)webView:(UIWebView *)webView 
    shouldStartLoadWithRequest:(NSURLRequest *)request 
    navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];    
    if ([[url scheme] isEqualToString:@”wopen”]){
        NSString *host = [url host];
        if (host != nil && [host isEqualToString:@”_blank”]){
            NSLog(@”new window!”);
        }
        return NO;
    }

    return YES;
}    

URLのscheme部分をみてwindow.openなのかどうかを見極め、遷移先を[URL path]で取って、あとは自分でどうにかする、という感じです。

自分もまだ基礎実験までしかやってないので、すごく見切り発車的エントリです w
このあとアプリに実装してみようと思います。 

2月 20

NSURLCacheでGoogleのトップページをキャッシュすると落ちる件

原因が特定できなくて困っていた、Googleのトップページでだけ発生する落ちバグがあったわけですが、なんとなく原因が特定できたくさい。
ちなみにこれは次のような現象。

あちこちのサイトをキャッシュしてみても、上記現象が起きるのはなぜかGoogleだけ。Googleとは言っても、ニュース(http://news.google.co.jp/)では大丈夫だったりします。

さらに、EXC_BAD_ACCESSは通常過剰releaseによって解放済みのメモリにアクセスすると起きるものですが、いくらretainかましても改善しませんでした。過剰releaseならGoogle以外でも発生するだろうし、ランダム要素があるなら http://www.google.co.jp/ で100%発生するというのはおかしいので、やはりGoogleさんのサイト構造に起因するものとして調べてみました。

とはいってもソースもないライブラリの中で起きてる例外なので調べようもなく、できることといえばファイルの構成をよくよくみてみるくらい。じーっ、とよく見てみると、試してみたサイトの中で、唯一 http://www.google.co.jp/ だけHTML5のmanifestファイルが含まれていました!

試しにmanifest指定を削ってみます。 

落ちない! これか!

理屈はわかりませんが、とりあえずmanifest指定を削ってしまうとよいようです。

(2/21追記)

いろいろいじってるうちにまた落ちるようになりました。違うらしい。

[iPhone]UIWebViewで困ってること

相変わらずUIWebViewと戯れているわけですが、いまのところ次の2点で困っていたりします。

  1. リンクを長押ししたときに表示されるActionSheetのカスタマイズ方法がわからない
  2. 新しいWindowを開こうとしていることを検知できない

iOS4.2だと、長押ししているとUIWebBrowserViewに longPressGestureCanTransitionToRecognizedState: というメッセージが送られてきているようなので、このselectorを書き換えてしまえばなんとかなりそうです。
ですが、UIWebBrowserView は非公開クラスなので、いつ実装が変わるかわからないのであまり適切な方法ではなさそうです。

一番困っているのは2のほうで、Aタグの場合(<a href=”hoge” target=”_blank”>等の場合)は検知ができているのですが、Javascriptでwindow.openしようとしていることが検知できなかったりしています。
AppStoreでタブブラウザをいくつかダウンロードしてみたのですが、window.openもきちんと別タブにするアプリもあり、なんらかの方法で検知できるようです。はてさて。。 

2月 19

[iPhone]サイトをローカル保存するための格闘

iPhoneを使っていて、最もイライラするのは通信状況。電波の受信感度はバッチリでも通信できないということが珍しくありません。
iPhoneのせい、というよりは、Softbankの電波のせいなわけですが。

iPhoneのSafariは(というか普通のブラウザはみな)一回みたページに戻る場合でも 最新かどうかを判断するため通信しようとするので、通信状況が不安定だとブラウジングがものすごくイライラします。

そこで一度みたページをすべてキャッシュし、キャッシュがあれば最新チェックもなにもかもすっ飛ばして表示してくれるアプリを作ろうとしています。が、これがなかなかに難儀しています。

いままでチャレンジしてみたこととしては、

(1) webView:shouldStartLoadWithRequest:navigationType: で保存すべきURLがわかるか?

  1. ドキュメントのURLはこれでわかるけど、画像やCSSなどのリソースについては呼び出されないのでわからない。さらにJavascriptでXHRとかしてるとそれは呼び出されてしまい、なにがページでなにがXHRかわからない。

(2) いっそのこと webView:shouldStartLoadWithRequest:navigationType: に飛んで来たURLからHTMLを取得し、中身をparseして画像やCSSなどのリンクをたどればできるんじゃないか?

  1. HTML、CSSのparseはできた。
  2. GoogleやYahooなどではCSSをJavascriptで後から追加するので、HTMLだけparseしても正しく表示できない。
  3. Javascriptのparseはさすがにちょっと無理。

(3) uiWebView:resource:willSendRequest:redirectResponse:fromDataSource: でなら画像やCSSも含めて呼び出されているっぽいからいけるんじゃない?

  1. 概ねこれで保存対象がわかるようではあるが、一部のリソースは呼ばれない模様。NSURLCacheのcachedResponse:forRequest: をチェックしているとwillSendRequestに飛んで来てないURLがcachedResponse:forRequest: されていたりしている。
  2. キャッシュしているリソースであればrequestのURLをfile://に書き換えたいが、それをすると落ちる。特にGoogleのトップページとか。
  3. 2が回避不能なのでこれだけでは解決できず。

(4) NSURLCacheでどうよ?

  1. キャッシュを返しても落ちない!キャッシュを返すのはこれでイケる。
  2. UIWebViewWebViewDelegateよりも呼ばれる数が多いので、こちらが呼ばれたものが本当に通信しにいっているリソースだと思われる。なので、これで保存対象がわかる。 
  3. storeCachedResponse:forRequest: はredirectされていても呼ばれてしまう模様。NSCachedURLResponseだけではredirectされたものかどうかの判断ができそうにないので、保存は無理。

という流れ。
結論としては、(4)でなんとかなりそう。
ただしstoreCachedResponse:forRequest: はredirectの絡みで使えないため、

という実装になりそうである。なかなか大変。 

2月 16

クラウドとセキュリティ

クラウドで構築するシステムを進めている中で、サービス毎の差異やセキュリティ対策の状況を調べていました。 主に検討しているのはGoogle AppEngine, Amazon EC2, あとは国内ものとしてニフティクラウド。PaaSとIaaSが混じってるのはご愛嬌。

個人的には、クラウドは最終的にPaaS化していくだろうと考えていますが、現段階ではGAEはなにかと融通が効かないというか、Webアプリ or サービスを公開する以外には適用できそうにないので、本命はEC2。

コスト、使えるサービスと自由度、可用性などについては特に問題ないと思うのですが、課題になっているのはセキュリティ。 自分的には、Amazonがセキュリティ施策でなにをしているのかを読むと充分だと思うのですが、これをクライアントに納得してもらうのが難しい。第三者機関からなにかの認定を受けているとかしていてくれれば安心材料になるのですが、SAS70くらいしかないですし、これってセキュリティの認定じゃないし。

そもそもクライアントには「自分の資産(データ)がどこにあるのかはっきりしないし、データが他人の管理下に置かれるわけだし、しかも他の企業と相乗りサービス」という漠然とした不安が根底にあるので、なおさら根拠を求められるわけです。

私としては、

自分ではこのように思うのですが、絶対的な保証や安全などは当然ないわけです。明確な課題を検討するのと違い、不安を払拭するのは難しいものですね。

ちなみにAmazon Web Serviceのセキュリティについてはこちら。
http://aws.amazon.com/jp/security/

2月 15

バッチ処理の高速化

5年程前に書かれた、他社が作成したシステムのバッチ処理があまりに不安定 かつ 低速だということなので、改善プロジェクトを実施しています。

となかなかハードルが高い案件です。なんといっても、このシステムは資料もなく、ソースも滅茶苦茶で可読性が悪く、コメントの要領を得なくて大変なのです。

まだ進行中ですが、

ほぼハードルをクリアできそうです。ひと月でここまでできればなかなかいい感じ。特に、設計/実装のコンセプトを保守会社の方々に伝えて彼らにやってもらう、という方法を採りながら目標達成できそうなのが素晴らしい。

自分で設計/実装してしまえば割と簡単なのですが、今回は今後の保守の兼ね合いから、保守会社の方々に設計を理解してもらう必要があります。設計/実装に彼らが深く関わることで、彼らに「自分のシステム」という認識をもってもらうことがとても重要だと考えているからです。

そうは言いつつ、アドバイザーに徹するだけで到達可能だと考えていたレベルに到達できるかどうか不安でした。でもなんとかなりそうです。こういう関わり方でもきちんと実績を残せると、自分も新しいプロジェクトの関わり方ができそうです。