アプリの申請でハマる
はじめてiPhoneアプリをAppleに申請してみました。
が、そこで思いっきりハマりまくりでした。
ハマったのは次の2点。
- 証明書を取らないとダメ
申請するアプリはCode Signされてないとダメで、Code Signは個人証明書を取得してないとダメなわけです。と言ってもこれは比較的有名なハマりポイントらしい。
- スクリーンショット不足
初めてのくせにiPhone, iPadのユニバーサルアプリにしてみたので、iPad用スクリーンショットもアップロードしないとmissing screenshot状態でストップしてしまいます。追加すればよいのですが、Localizeしてると、言語毎にスクリーンショットも独立しているので、全ての言語にiPad用スクリーンショットをアップロードしないといけません。
どハマりしたのはスクリーンショットで、アップロードしたのに状態が変わらず焦りました。こんな状態になるのはユニバーサルかつLocalizeしたときくらいでしょうからレアケースだと思いますが。
xibのローカライズでハマる
設定画面など、あちこちに文字列を埋め込んだ画面はいちいちコードでNSLocalizedStringするのが面倒なので、xibをローカライズしてしまうのが簡単だと思います。
はじめてxibをローカライズしてみたのですが、色々ハマりました。
- xibはサブフォルダの中でローカライズしてはいけないらしい。
たまたまユニバーサルアプリでやっていたので、xibがiPhoneサブフォルダの中に出来てしまいました。そこに置いたままローカライズすると認識されない模様。 文字列リソースをローカライズするとプロジェクトフォルダの直下にJapanese.lprojができますが、そこに入らないとダメみたい。
- サブフォルダの残骸はFinderで削除しないとダメみたい。
一回xibをサブフォルダで作ったあと、サブフォルダにできたJapanese.lprojの中にxibが残ったままにすると、Xcode上でxibを削除しても、ビルド時に掴んでしまうくさい。
サブフォルダに作ったxibと同じ名前でプロジェクトフォルダ直下に作り直しても、残骸が残ったままだとそっちが優先して使われているようで、うまく動かなくなりました。
それにしても、Xcodeはメジャーバージョンが変わるたびに作法がガラリと変わるので、ネットで調べてもどれがいまも使える方法なのかがわかりづらいですね。
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
続: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つの組み合わせ。
- UIWebViewの webView:willClickElement: をoverrideして、elementからtarget属性を探す。(Aタグ用)
- 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
このあとアプリに実装してみようと思います。
NSURLCacheでGoogleのトップページをキャッシュすると落ちる件
原因が特定できなくて困っていた、Googleのトップページでだけ発生する落ちバグがあったわけですが、なんとなく原因が特定できたくさい。
ちなみにこれは次のような現象。
- Googleのトップページ(http://www.google.co.jp/)の内容を保存しておき、通信しないでUIWebViewで表示しようとすると落ちる。
- キャッシュの仕方はNSURLCacheでやろうと、別の方法を使っても発生する現象は同じ。
- 落ちるのはHTTPProtocol::createStreamのあたり。起きているのはEXC_BAD_ACCESS
あちこちのサイトをキャッシュしてみても、上記現象が起きるのはなぜか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点で困っていたりします。
- リンクを長押ししたときに表示されるActionSheetのカスタマイズ方法がわからない
- 新しいWindowを開こうとしていることを検知できない
iOS4.2だと、長押ししているとUIWebBrowserViewに longPressGestureCanTransitionToRecognizedState: というメッセージが送られてきているようなので、このselectorを書き換えてしまえばなんとかなりそうです。
ですが、UIWebBrowserView は非公開クラスなので、いつ実装が変わるかわからないのであまり適切な方法ではなさそうです。
一番困っているのは2のほうで、Aタグの場合(<a href=”hoge” target=”_blank”>等の場合)は検知ができているのですが、Javascriptでwindow.openしようとしていることが検知できなかったりしています。
AppStoreでタブブラウザをいくつかダウンロードしてみたのですが、window.openもきちんと別タブにするアプリもあり、なんらかの方法で検知できるようです。はてさて。。
[iPhone]サイトをローカル保存するための格闘
iPhoneを使っていて、最もイライラするのは通信状況。電波の受信感度はバッチリでも通信できないということが珍しくありません。
iPhoneのせい、というよりは、Softbankの電波のせいなわけですが。
iPhoneのSafariは(というか普通のブラウザはみな)一回みたページに戻る場合でも 最新かどうかを判断するため通信しようとするので、通信状況が不安定だとブラウジングがものすごくイライラします。
そこで一度みたページをすべてキャッシュし、キャッシュがあれば最新チェックもなにもかもすっ飛ばして表示してくれるアプリを作ろうとしています。が、これがなかなかに難儀しています。
いままでチャレンジしてみたこととしては、
(1) webView:shouldStartLoadWithRequest:navigationType: で保存すべきURLがわかるか?
- ドキュメントのURLはこれでわかるけど、画像やCSSなどのリソースについては呼び出されないのでわからない。さらにJavascriptでXHRとかしてるとそれは呼び出されてしまい、なにがページでなにがXHRかわからない。
(2) いっそのこと webView:shouldStartLoadWithRequest:navigationType: に飛んで来たURLからHTMLを取得し、中身をparseして画像やCSSなどのリンクをたどればできるんじゃないか?
- HTML、CSSのparseはできた。
- GoogleやYahooなどではCSSをJavascriptで後から追加するので、HTMLだけparseしても正しく表示できない。
- Javascriptのparseはさすがにちょっと無理。
(3) uiWebView:resource:willSendRequest:redirectResponse:fromDataSource: でなら画像やCSSも含めて呼び出されているっぽいからいけるんじゃない?
- 概ねこれで保存対象がわかるようではあるが、一部のリソースは呼ばれない模様。NSURLCacheのcachedResponse:forRequest: をチェックしているとwillSendRequestに飛んで来てないURLがcachedResponse:forRequest: されていたりしている。
- キャッシュしているリソースであればrequestのURLをfile://に書き換えたいが、それをすると落ちる。特にGoogleのトップページとか。
- 2が回避不能なのでこれだけでは解決できず。
(4) NSURLCacheでどうよ?
- キャッシュを返しても落ちない!キャッシュを返すのはこれでイケる。
- UIWebViewWebViewDelegateよりも呼ばれる数が多いので、こちらが呼ばれたものが本当に通信しにいっているリソースだと思われる。なので、これで保存対象がわかる。
- storeCachedResponse:forRequest: はredirectされていても呼ばれてしまう模様。NSCachedURLResponseだけではredirectされたものかどうかの判断ができそうにないので、保存は無理。
という流れ。
結論としては、(4)でなんとかなりそう。
ただしstoreCachedResponse:forRequest: はredirectの絡みで使えないため、
- cachedResponse:forRequest: で保存対象を特定
- 自力で保存
- 自力保存したリソースが要求されたらそれをcachedResponse:forRequest: で返す
という実装になりそうである。なかなか大変。
クラウドとセキュリティ
クラウドで構築するシステムを進めている中で、サービス毎の差異やセキュリティ対策の状況を調べていました。 主に検討しているのはGoogle AppEngine, Amazon EC2, あとは国内ものとしてニフティクラウド。PaaSとIaaSが混じってるのはご愛嬌。
個人的には、クラウドは最終的にPaaS化していくだろうと考えていますが、現段階ではGAEはなにかと融通が効かないというか、Webアプリ or サービスを公開する以外には適用できそうにないので、本命はEC2。
コスト、使えるサービスと自由度、可用性などについては特に問題ないと思うのですが、課題になっているのはセキュリティ。 自分的には、Amazonがセキュリティ施策でなにをしているのかを読むと充分だと思うのですが、これをクライアントに納得してもらうのが難しい。第三者機関からなにかの認定を受けているとかしていてくれれば安心材料になるのですが、SAS70くらいしかないですし、これってセキュリティの認定じゃないし。
そもそもクライアントには「自分の資産(データ)がどこにあるのかはっきりしないし、データが他人の管理下に置かれるわけだし、しかも他の企業と相乗りサービス」という漠然とした不安が根底にあるので、なおさら根拠を求められるわけです。
私としては、
- 物理的に完全に分離されていないとしても、暗号化した上で分散して保存されていれば、第三者が不正アクセスでデータを完全な形で引き抜くことはできないでしょうし、複数のデータセンターに冗長化して保存されている時点で物理的なデータ保管場所にはさほど意味がないと思ってます。
- 運用会社内部からの不正アクセスはホスティングだろうとクラウドだろうと付きまとう問題なので、クラウドという環境の特性ではないと思ってます。
- 物理環境としては、他の企業と空間をshareするデータセンターよりも、物理環境に立ち入るのはサービス提供業者だけに限定されているクラウドのほうが入退室管理も厳密にできると思うので、ちょっと前までよくやっていた、自社でサーバを購入してどこかのデータセンターにハウジングしてもらうより、よっぽどセキュアだと思ってます。
- 電子的セキュリティも重要ですが、ソーシャルエンジニアリングのほうがよっぽど怖いので、システム化範囲を削って電話対応等を増やすほうが危ないと思ってます。
自分ではこのように思うのですが、絶対的な保証や安全などは当然ないわけです。明確な課題を検討するのと違い、不安を払拭するのは難しいものですね。
ちなみにAmazon Web Serviceのセキュリティについてはこちら。
http://aws.amazon.com/jp/security/
バッチ処理の高速化
5年程前に書かれた、他社が作成したシステムのバッチ処理があまりに不安定 かつ 低速だということなので、改善プロジェクトを実施しています。
- 懸案課題が解消すること
- パフォーマンスが改善されること
- 現在保守している会社が保守していけること
- 保守性が向上すること
となかなかハードルが高い案件です。なんといっても、このシステムは資料もなく、ソースも滅茶苦茶で可読性が悪く、コメントの要領を得なくて大変なのです。
まだ進行中ですが、
- 長らく不思議現象だった懸案課題も改善できたようですし、
- 保守会社に新しい設計を理解してもらえたようですし、
- 新しい設計では現行システムの20倍速くらいにパフォーマンス改善されたようですし、
- いままで明文化されていなかった要件やフローが資料化できるネタが洗いだせたようですし、
ほぼハードルをクリアできそうです。ひと月でここまでできればなかなかいい感じ。特に、設計/実装のコンセプトを保守会社の方々に伝えて彼らにやってもらう、という方法を採りながら目標達成できそうなのが素晴らしい。
自分で設計/実装してしまえば割と簡単なのですが、今回は今後の保守の兼ね合いから、保守会社の方々に設計を理解してもらう必要があります。設計/実装に彼らが深く関わることで、彼らに「自分のシステム」という認識をもってもらうことがとても重要だと考えているからです。
そうは言いつつ、アドバイザーに徹するだけで到達可能だと考えていたレベルに到達できるかどうか不安でした。でもなんとかなりそうです。こういう関わり方でもきちんと実績を残せると、自分も新しいプロジェクトの関わり方ができそうです。