なるようになるかも

力は多くの場合、その人の思いを超えない。

コンテナViewControllerについて

A-Liaison BLOG: Container View Controllerを作ってみよう

この記事の間違いが多すぎてやばいので。

iOS 5からはこのContainer View Controllerを自作する事が可能になりましたが、実装が面倒なのと大体の場合においてUIKitが用意しているContainer View Controllerを使うかcocoapodsあたりからそれっぽいライブラリを拾ってくれば解決するためかあまり具体的な実装方法が話題になっていないようです。

iOS5の頃は単に英語資料しか存在しなかったので、普通の人は存在を知らないし、存在を知っている人は英語をある程度読めるので、海外の記事や実装を直接読んでいただけじゃないのかなぁと思う。マジでスライドで概要を説明している人が1人いるだけとかそういうレベルだったんですよ…。

UIViewControllerプログラミングガイドに邦訳が載ったiOS6以降だと具体的な実装方法は日本語で読めるようになりましたし、実装方法を話題にしている人も圧倒的に増えたと思うんですけど…。

本題

didMoveToParentViewController:willMoveToParentViewController:の実装を間違える人は多いです。そもそもカスタムなコンテナUIViewControllerの実装者の責務(must)であるにも関わらず、呼んでないコードを見たこともあります。

上の記事も間違えています。

しかしご安心ください。まず問題になりません。

なぜか。

全ての初期化処理を単一のライフサイクルメソッドに押し込める、viewDidLoad病か、viewDidAppear病の患者が極めて多く、didMoveToParentViewController:のタイミングで何らかの処理を行うこと自体が極めて稀なためです。

独創的なUIViewControllerのサブクラスを基底クラスとして案件ごとに用意して、独自の初期化処理を行うようなコードも見ます。回転系のライフサイクル系メソッドがバージョンによって変わったり、UITableViewControllerを継承する必要が出てきて詰むやつです。

will/didMoveToParentViewController:とは

ViewControllerのライフサイクルメソッドのひとつです。

UIViewControllerがネストする構造になっているときに、これから子として追加、もしくは親から削除されるタイミングで何らか処理を行いたい場合に記述できます。

引数のUIViewControllerは親となるViewControllerです。nilの場合、親から取り除かれることを意味しています。

何に使うのか

独自のコンテナViewControllerを作る場合、子から親へ何らかの通知を行いたい場合があります。

任意のタイミングでparentViewControllerプロパティを利用し、直接親のViewControllerへアクセスすることは可能なのですが、何らかのプロトコルに準拠させて操作に制約を付けた方がスマートです。

そのような実装を考えたとき、いつdelegateとして親のViewControllerへの参照を保持/解放するかを考えると、親子関係が紐付く/解除されるこのタイミングが最も適切です。

addChildViewController:を呼び出す親側で、delegateの登録を行うことも当然できます。しかし親がインターフェースを規定してしまうので、子の実装に制約がかかります。

一方、子側でdelegateの登録を行う場合は後からプロトコルを増やしたり、delegateの登録を行わない特殊な子の存在を許容できたりと、柔軟性が大きく変わります。

ただしこれは、コンテナViewControllerが正しく実装されている という前提での話です。たとえばdidMoveToParentVieController:を呼び出さないコンテナViewControllerでは、didMoveToParentVieController:が呼び出されるのはViewControllerの階層から削除されるタイミングだけです。諦めましょう。

コンテナViewController実装者の責務

2年くらい前に書いた記事のコピペですけど、

  • addChildViewController:の後にdidMoveToParentVieController:
  • removeFromParentViewController:の前にwillMoveToParentViewController:

を、それぞれ手動で呼び出す必要があります。一方で、

  • addChildViewController:の前のwillMoveToParentViewController:
  • removeFromParentViewController:の後のdidMoveToParentViewController:

は、デフォルトでは自動的に呼ばれますので、何もしなくていいです。

なぜ手動で呼び出す必要があるメソッドと、そうでないメソッドがあるのでしょう?

答えは簡単で、ViewController追加/削除のトランジションがいつ始まり、いつ終わるのかはシステムには分からないからです。

addChildViewController:が呼び出されたということは、これからViewControllerのトランジションが始まるのだということはシステムにも分かります。そこでwillMoveToParentViewController:は自動的に呼び出されます。コンテナ実装者はトランジションが完了したタイミングで、didMoveToParentVieController:をコールする必要があります。特にアニメーションなどない場合は即座に呼び出して問題ありません。

removeFromParentViewController:の考え方も同様です。removeFromParentViewController:が呼び出されるタイミングは既にViewControllerのトランジションが終了し、ViewControllerの階層から取り除かれた後なので、didMoveToParentViewController:は自動的に呼び出してくれます。しかしトランジションが始まるタイミングはシステムには分からないので、コンテナ実装者はnilを引数にしてwillMoveToParentViewController:を呼び出す必要があります。

begin/endAppearanceTransition:animated:について

呼び出さなくていいです。

なぜ存在するのか

iOS5ではautomaticallyForwardAppearanceAndRotationMethodsToChildViewControllersiOS6以降はshouldAutomaticallyForwardAppearanceMethodsというBOOLを返すメソッドがあり、コンテナViewController実装者は必要に応じてこのオーバーライドすることができます。

デフォルトはYESですが、NOにすることで

  • viewWill/DidAppear:animated
  • viewWill/DidDisappear:animated
  • willRotateToInterfaceOrientation:duration:など回転系のメソッド(バージョンによって違いがありすぎるので割愛)

などのトランジションが発生した際の通知を、子ViewControllerに伝播させないようにできます。(逆に言えば、通常は自動的に子に伝播されます)

親と子とでトランジションのタイミングを変えたい場合、または複数の子ViewControllerにディレイを掛けてトランジションさせたい場合など、子ViewControllerの外観の制御を親が完全にコントロールしたい場合のみ利用する特別なオプションです。まず考慮に入れなくていいので、確かに記憶から抹消しても構いません。

ただ以下のことを知らずにコンテナViewControllerを実装するのは論外です。

このとき、自動的に呼び出されなくなったwillRotateToInterfaceOrientation:duration:などはコンテナViewControllerが任意のタイミングで、手動で呼び出すことができます。

しかし、viewWill/DidAppear:animatedおよびviewWill/DidDisappear:animatedについては、絶対に手動で呼び出してはいけません。これらのメソッドを呼び出す特権は、システムのみが持ちます。

そこでこれらのメソッドを直接呼び出す代替として、begin/endAppearanceTransition:animated:を経由して通知を行うのです。

ですので、shouldAutomaticallyForwardAppearanceMethodsを記憶から抹消する場合、beginAppearanceTransition:animated:の存在も一緒に抹消してください。

shouldAutomaticallyForwardAppearanceMethodsとしてYESを返す場合(つまりデフォルト)では、トランジションの開始と終了の通知は、will/didMoveToParentViewController:を適切なタイミングで呼び出すだけで構わないのです。

余談

iOS5でviewDidAppear:内でaddChildViewController:するとChildViewControllerのviewWillAppear:が呼ばれない - Qiita

shouldAutomaticallyForwardAppearanceMethodsメソッドの命名から考えると、外観変更系のメソッドは「子に転送される」のでiOS5の実装が正しいような。

viewDidAppearaddChildViewController:するということは、既に終了している親のviewWillAppear:を子側へ転送するタイミングがないわけで、これでちゃんと呼び出してくれるiOS6以降がイレギュラーな感じ。

AndroidのHttpURLConnection。

これはAndroidじゃなくてJDKのインターフェースの設計の問題なのですが、HttpURLConnectionは入出力エラーが発生した際にIOExceptionを投げるという規定があります。

問題は、HTTPステータスコードが400番台ないし500番台のコードのボディを読もうとした際に、getInputStream()を使うと入出力エラー扱いされてIOExceptionが発生することです。

最近のRESTfulなサーバーインターフェースの設計だと、HTTPステータスコードに意味を持たつつ、レスポンスボディにコンテンツを渡すのが主流ですが、HttpURLConnectionのちょっとしたサンプルでは400 Bad Request401 Unauthrizedが返されることを考慮していないことが多いです。

HttpURLConnectionを本格的に使おうとすると、大抵ここで躓くことになります。

正しい実装方法は…?

サーバーがHTTPステータスコード上でエラーを返したときは、getErrorStream()でしかレスポンスボディを読めません。

定番なのは getResponseCode()を読んで、getInputStream()を読むか、getErrorStream()を判定する実装です。

しかしHttpURLConnectionはどのHTTPステータスコードが入出力エラーに該当するのかを規定していないので、「実装上そうなっている」という経験則で400番台と500番台を特別扱いすることになり、これが非常に気持ち悪いです。

Androidでは FileNotFoundExceptionをキャッチした上で、getErrorStream()を読むという実装方法が紹介されていることがあります。

FileNotFoundExceptionをスローすることはAndroidのコードを読めば分かるのですが、404 NotFound以外でもFileNotFoundExceptionをキャッチして処理するのは直感に反していますし、ドキュメントに記載されている内容でもないので、将来的に変更されないとも限りません。あまり推奨された方法ではないと思います。

なおFileNotFoundExceptionIOExceptionのサブクラスなので、IOExceptionより先にキャッチする必要があります。

401 Unauthrizedの辛さ

アプリとサーバー間でやり取りする場合、認証処理が入るケースが多く、セッションのタイムアウトや、パスワード変更などでサーバーが401エラーを返し、それにハンドリングしなければならないケースはよくあります。

自前で実装する場合にはいろいろ闇があるので注意してください。

まず、前述した getResponseCode()で識別する方法ですが、古いAndroidでは使えません。

FROYO以前のAndroidには、HTTPステータスコードで401が返却された時点でコネクションをcloseするため、getResponseCode()の返り値が-1になる致命的なバグがあるためです。

古いAndroid端末もターゲットにする場合は、絶対にHttpURLConnectionを使わない でください。他にもコネクションプール汚染の深刻なバグもあります。Volleyの実装を見てもGoogle自身ですら、FROYO以前のAndroidではApache HTTP Clientで動作するようにしています。

あと、ヘッダにWWW-Authenticateを付与してくれない場合にIOExceptionになるケースが非常に厄介です。これは最近のAndroidでも起きるような…。

これについて言えば、仕様上は401 Unauthrizedを返すときは、WWW-Authenticateの付与が MUST とされているので正しい挙動なのです。サーバーとクライアント間で認証方法が自明であっても付与しましょう。しかしRFC 2616を理解せず、「流行ってるからRESTにしようぜ」的なノリでサーバーを実装する人は後を絶ちません。

稀に、不正な認証リクエストに対してWWW-Authenticateヘッダなしで401を返すサーバーも存在します。有名どころではTwitterくらいしか聞きませんけども。

Androidの通信処理に何使えばいいのか分からないって話。

特に結論はないです。本当に分からないので。

ソケットレベルまで踏み込むと、途端に面倒になってどのライブラリを使っても手に負えませんし、単にGETとかPOSTとかする分には正直どれ使ってもそこまで変わらない気がしてます。

それより自己署名証明書の検証を無視して通信を行うと端末が爆発するライブラリが必要だと思います。

Apache HTTP Client

みんなお馴染みDefaultHttpClient。色々なライブラリがあるけど、最終的にはここに行き着いていることが多いです。

しかし「Apache HTTP Clientとは何なのか」、という説明はあまり見ない気がします。

自分も「Apacheソフトウェア財団のトップレベルプロジェクトとして運用されている、RFCを満たす実装を目指したJava向けのHTTPインターフェース」という超ふんわりとした認識しかないです。

かなり巨大なライブラリで、全貌を理解するには、HTTPの仕様に関する理解が必要な感じ。

Android SDKの中にも入ってるから特に意識せず使ってる人も多いと思うんだけど、Android SDKに入っているApache HTTP Clientは、バージョン4.0の不完全な状態のものらしい?そのため、可能な限り最新の4.3との互換性を保つAndroid専用のサブライブラリがあります。

また、バージョンごとに大幅にAPIのインターフェースが変わる印象があって、Android以前の人が書き残したコードは、Deprecatedの嵐に見舞われていることが多いです。

Multipartの送信など面倒なことをやろうとする場合に、SDK内臓ではないApache HTTP Clientをライブラリとして選択するケースがあります。記述の冗長さがやはり大きな欠点ですが、RFCを意識したAPIは、仕様が共通言語の人たちにとっては苦にならないのかもしれません。

また、「Android向け」を謳うライブラリには通信量削減のために勝手にgzip圧縮などを行うものが多く、ほとんどの場合その方が効率的に通信を行えるのですが、「プログラマが書いた処理を厳密に実行して欲しい」という業務系ニーズでも強い印象です。

AndroidHttpClient

API Level8のころの遺物みたいなもの?それ以前では使えません。

Android SDKに組み込まれた「Apache HTTP ClientをAndroidに最適化したもの」という説明がされているDefaultHttpClientの亜種です。

コンテンツの送受信にgzip圧縮をかけていたり、タイムアウトの設定が携帯端末に合わせたものに調整されていますが、通信処理そのものはApache HTTP Clientに委譲しています。

また、API Level11以降のAndroidでは、UIスレッドで通信を行うとNetworkOnMainThreadExceptionでクラッシュしてくれる親切設計になりましたが、この時代は通信が平気でUIスレッドを固めていたので、プログラマの矯正の意味合いもありました。

これ使ってる人って本当にいるんです?

HttpURLConnection

java.net.HttpURLConnectionですが、AndroidAPIは、当然ながらインターフェースが同じなだけでJDK実装とは別物です。

この事実は、基本的なコレクションクラス等でバグに遭遇することはないので、まず気にすることではないのですが、暗号論的擬似乱数生成器のはずのSecureRandom脆弱性があった、などの闇が広がっています。

初期のHttpURLConnectionもそうした深淵の一つだったのですが、API Level10のころに大々的に「AndroidのHttpURLConnectionも進化したからみんな使ってね!」とアピールしていた記憶があります。もちろんAPI Level10未満の端末ではバグが残ったままなんですけどね…。

そのときアピールしてたメリットは、デフォルトでgzip圧縮してくれることでした。やっぱりAndroidHttpClient使ってる人いないのでは?

もともとはJDKのインターフェースだけあって、「DefaultHttpClientの生成方法がバージョンによってまったく別物になった」みたいな混乱はないですし、ちょっとした用途に使う分には十分です。

しかし、初期の試作にHttpURLConnectionのラッパーを作って楽をしようとした結果、後々複雑な通信をすることになってハマっているケースとか、OSのバージョンによって異なるIOExceptionのエラー文言を判定してる闇なコードを見たりします。

Async Http Client

Java用の非同期なHttpClient。

ちょっと前にAndroidの生きたライブラリとして紹介されてましたけども、Androidだと似たコンセプトの後者の採用率の方が高い気がします。

どうせAndroidではClosableとか使えませんからね…。

Android Asynchronous Http Client

中身としては、Apache HTTP Clientを、ExecutorServiceによるスレッドプールで非同期処理してくれるというものです。軽量なライブラリであることが特徴で、有名なアプリでも採用されており実績も豊富です。

ネットワークのために定型的な非同期処理を書く面倒さから解放してくれます。

反面、コールバックハンドラを記述する方式は、レスポンスをデータベースに格納するなど、非同期処理と連結させる必要がある場合に可読性を著しく損う印象があります。

簡単な処理は本当に簡単に書けますが、処理が複雑になることが予想される場合には、自前で非同期処理を書いたほうが見通しが良くなるかも?

Google HTTP Client Library for Java

冗長なApache Commonsの記述を、簡易に書くためのラッパーライブラリです(HttpURLConnectionでも使えるみたいです)。

ただしHTTP通信自体が主眼ではなくて、それより上位のユースケース、実際的にはXMLJSONシリアライズ/デシリアライズ等をアノテーションなどで簡潔に書くためのライブラリといった認識です。

このライブラリを直接使う機会はあまりないと思いますが、Googleのサービスとの接続に使われるGoogle APIs Client Libraryや、OAuth2.0による認可を簡単に行えるGoogle OAuth Client Libraryはこれがベースになっているので、間接的にお世話になっている人は多いんじゃないかなぁという印象です。

AndroidだとGoogleサービスとの連携がシームレスだから、関係ないんじゃないの?」と思うじゃないですか。思いましたよ。しかしGoogle Drive Android APIとか凄まじく貧弱なので、それなりなことをしようとすると結局こっちを使うハメになるんですよね…。

Volley

いい意味でも悪い意味でも「えー、今時Volley使ってないのー」みたいなノリを感じます。

GoogleによるAndroid向けのネットワークライブラリと銘打っているだけあって、HTTPURLConnectionの闇を打ち払ったり、ImageViewなどActivityのコンポーネント群との連携は抜群で、特に厄介なBitmap周りの処理を簡潔にしてくれますが、それだけではなく非常に汎用性が高いです。

ただやや概念が特殊なので、最初のとっかかりが悪いのと、下手に拡張性が高いために独自のリクエストクラスを量産した結果、Volley職人にしか理解ができないみたいな状態になりかねないのが二の足を踏む要素です。

AndroidでSNS連携する方法

すごく今更な話題なんですけど、iOS8で「AndroidのIntentみたいに簡単に共有できるようになる!」っていう話をよく聞いたので。

OS標準で共有機能のあるiOSアプリをAndroidに移植を行う場合に、「TwitterFacebookに投稿するボタン作って。IntentのあるAndroidなら簡単でしょ?」みたいに言われるんですが、現実は割と甘くないです。

確かに、ACTION_SENDなどで暗黙のインテントを飛ばすだけで勝手に投稿可能なメーラーSNSクライアントが一覧表示されます。

ただし、ACTION_SENDによる共有の考え方では、受け取ったアプリが全てハンドリングしてくれる利点と引き換えに、「Twitterのみに投稿したい」という制約条件を付与できないのです。

加えてSNSごとの文字数制限や、後述するFacebookの独自仕様などがあるため、送る側が何も考えなくても、インテントを受け取ったアプリでどうにかしてくれる…というわけでもありません。

Twitterに投稿したい場合

公式アプリのパッケージ名を指定してACTION_SEND

公式アプリ以外を使ってるユーザーを完全無視してもいいならこの方法が使えます。

URI.parse()を使う

https://twitter.com/intent/tweet?text=に送信したいメッセージを付与してURI.parse()します。大抵のTwitterクライアントはこのURLをハンドリングしてくれるはずです。

http://スキーマなので、普通のブラウザも反応するのが難です。このためTwitterクライアントがインストールされていないことを検知することはできませんし、インストールされていたとしてもログイン状況がどうなっているのかは把握できません。

Twitter4Jを組み込む。

そのため、Intentを投げ捨ててTwitter4Jを使うの最も現実的な解となっているような気がします。

それぞれのアプリがWebViewでログインさせている現状って正直微妙だと思うんですよね。

Account ManagerからOAuth2トークンを引っ張ってくるとか、もうちょっとマシな方法がある気がするのですが…。

Facebookに投稿したい場合

公式アプリのパッケージ名を指定してACTION_SEND

Intent連携を否定しているのがFacebookです。やれることは以下の2点のみです。

  • ウェブサイトへのリンクをシェアする
  • 画像をシェアする

「リンクもしくは画像」以外のものをIntent.EXTRA_TEXTに付与した場合、 その内容は破棄されます

なぜなら、Facebookには「 ウォールに投稿する文章はユーザーが制御するべき 」というポリシーがあるため、アプリ側から文字列を事前にプリセットする行為そのものが非推奨なのです。

じゃあなんでiOSのSocial.Frameworkだとできるの…。

Facebook SDKを使う

Android用の公式SDKを使う場合も、上記制約は変わらないため、FacebookDialog.ShareDialogではテキストのプリセットはできません。

何らかの文字をプリセットしたい場合、投稿用のDialogFragmentを自作しなければなりません。

LINEに投稿したい場合

LINEもIntent連携を無視した独自ルールを採用しています。

具体的には、line://msg/text/URI.parse()以外の方法で連携する手段がありません。

それiOSのURLスキームだと思うんですけど…。

UIKit徹底解説読んでる。

StoryBoardに乗り遅れてる感があるので手にとってみたのですけど、いい本です。まだ完全に読み込めてないのでざっくりとした感想ですけども。

UIKit徹底解説 iOSユーザーインターフェイスの開発

UIKit徹底解説 iOSユーザーインターフェイスの開発

特にUIFontDescriptorまわりについては、これほど丁寧な解説は存在しないと思います。

StoryBoardを駆使してコード量を減らしつつTODOアプリを作る章など、読み応えのある本でした。

iOS6と7による違いのトラップについても随所に述べられています。

ただUIKitの処理について徹底的に書かれているかというと、ヒットテストビューやレスポンダチェーンについての解説はイベント処理ガイド(iOS用)をほぼなぞりつつ、ジェスチャレコナイザのiOS7用メソッドに関する解説が付け足されているような感じでした。

いくつか食い足りない感じの場所もありました。

UIAppearanceの話

UIAppearanceの外観設定についても割とあっさりでした。

実行時にしか反映されないから、StoryBoardとの相性は悪いのでしょうがないのかも。

そもそもクラスリファレンスにどのプロパティがUIAppearanceに対応してあるのか書いていないという致命的な問題があって、ヘッダファイルにUI_APPEARANCE_SELECTORマクロが定義されているかを追っかけて回るわけですけど、SDKバージョンによって予告なく変更されたり、指定されていなくても実は使えたりとか、iOS7でtintColorの解釈が変わったりとか…。

まともに使えたものじゃないですんですよね。

それでも現状、UIButtonの見栄えを統一したいときは、resizableImageWithCapInsets:resizingMode:で縮尺の設定をした画像を、UIAppearance経由でsetBackgroundImageなどに設定するのが、便利だし実装コストも低い方法だと思っているのですけれど、IBやSBに反映されないというのはやはり痛くて、iOS7に置き換わっていくとアセットカタログのスライシングに流れていくんでしょうか。

iOS7以降ではtintColorでアプリのテーマカラーを決めて欲しいように見えますし。

AutoLayoutの話

この本はAutoLayoutをStoryBoardで扱う方法について相当力を入れて解説していますけども、コード上で扱う方法についてはVisual Format Languageで少し言及しているだけです。

Visual Format Languageのヒドさは今更言及する必要はないかと思いますが、ではこれを使わずまともに制約を書こうとすると、これがまた難解というか面倒でやってられないんですよね…。

SB任せでも基本的には問題ないんですけど、制約ベースでアニメーションをしようと思うと、制約を修正(場合によっては一度消してから再生成)して、アニメーションブロックでlayoutIfNeededを呼ぶ必要があるので、複雑な制約が付いているビューを動かす状況など、コードベースの制約についての理解もそれなりに必要なんじゃないの?という気がします。

ところで本書にも制約ベースのアニメーションに関する記述が若干載っていて、そこでは制約を相対的に操作して、layoutSubViewsを呼び出しているのですけれど、UIViewのクラスリファレンスなど読むに、

You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.

layoutSubViewsの直接呼出しって作法として微妙なんじゃないのかな?

ここでのshould notは「推奨されない」程度の意味だと思いますけど。

XCode6の話

NDAの関係で白昼夢なんですけど、XCode6で個人的に一番インパクトのある変更点は、IBやSBがdrawRect:を解釈してくれることなんですよね。

今までカスタムビューを作っても、IBやSB上では透明なビューにしか見えなかったのですけど、これで面白いコントロール系ライブラリがまた増えてくる可能性があります。もっともコントロール系は暗黙アニメーションを利用するために、drawInContext:で実装する方が多い気がしますけど…。

本書ではカスタムビューの説明をかなり端折ってあって、そこがちょっと勿体無いなーという感想でした。

ついでに言えば、drawRect:のライブレンダリングの説明には、intrinsicContentSizeを解釈してくれるという記述は見当たらないから、AutoLayout上にカスタムビューを配置するにはプレースホルダーを指定しなきゃいけないという残念な仕様はそのままな気がします。

iOS6/7のviewDidLoadが呼ばれるタイミングの違い

viewDidLoadが呼ばれるタイミングは、UIViewControllerviewプロパティが初めてアクセスされたタイミングである」と公式ドキュメントに書いてあります。

しかし、 いつ・誰がviewプロパティにアクセスするのか についてはフレームワーク内部なのでブラックボックスとなっています(スタックトレースを追えばすぐ分かるんですけど)。

例えばこういう書き方をしたとします。

HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
vc.hogeLabel.text = @"hoge";

このコードはiOS6ではラベルが書き換わるのだけど、iOS7ではvc.hogeLabelnilと評価されるため、動作しません。ここでは画面遷移をコードで管理していますが、storyboardを用いて、[segue destinationViewController]で取得した場合でも同じことが言えます。

つまるところ、iOS6ではpushViewController:animated:presentViewController:animated:が実行された時点でviewプロパティにアクセスされ、viewDidLoadがコールされていたのに対して、iOS7ではそのスタックを抜けた時点で初めてviewプロパティにアクセスされるよう変更されました。

遷移処理をその場で行わず、メインスレッドにキューイングするように変更されたのでしょうね。

こんなトラップにハマる人はまずいないと思うのですけど、イニシャライザやviewDidLoadで時間の掛かる処理(ネットワーク通信など)を行い、プログレス表示を行いたい場合、本来は非同期処理にしてコールバックを受け取るような仕組みを作る必要があります。

これを同期的に処理を書こうと考えたのか、

[self showProgress]; // keyWindowに対してプログレス用のviewをaddする
HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
[self hideProgress]; // addしたviewをremoveする

と書いてあるコードが存在し、もはや期待した動作をしていませんでした。ていうかこんな発想自体ないわー。

バッドノウハウですが、

HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
vc.view;
vc.hogeLabel.text = @"hoge";

と書いて無理やりviewDidLoadと呼びだすこともできます。ただしコンパイラの最適化や今後のiOSバージョンによって挙動が変わる恐れがありますし、適切に非同期処理を書くべきです。

iTunes Connectの変更の話。

TechCrunchの記事(というかその翻訳)が酷かったので。

原文タイトルは「Apple Developers Must Now Agree To Ad Identifier Rules Or Risk App Store Rejection」。

Appleデベロッパーは今後広告識別子の規則に同意しなければ、AppStoreからリジェクトされるだろう」という話。

職業訳者ならばもっとこなれた日本語にするのでしょうけれど、「同意が必要となった」を「規則遵守が義務化」という頭痛が痛い表現にはしないだろうし、間違ってもリスクを「違反者は拒絶される」と超訳したりはしないでしょう。

Apple悪帝国」というシナリオの文脈で翻訳したのだと思うんだけど、別に今回の件はそういう話じゃないと思うんだよね。

iTunes Connectの変更点

具体的に何が変更されたのかといいますと、

  • Advertising Identifierとは何か?の説明
  • Advertising Identifierを利用しているか?ONにした場合、以下の追加項目

が追加されました。利用用途については現在の典型的な、ユーザーのプライバシー保護を考える上で容認される利用法をカテゴライズしたものです。

このアプリがAdvertising Identifierを利用する目的 (当てはまるもの全てを選択)

  • このアプリ内で広告を提供するため
  • 提供された広告によって、このアプリがインストールされるため
  • 提供された広告によって、このアプリでアクションを実行するため

2、3番目はいわゆる「リワード広告」を指しています。

Advertising Identifierを使ってどの広告からアプリがインストールされたのか検知したい場合、いわゆるCPI広告を組み込む場合には2番目をチェックする必要があります。3番目のユースケースが具体的にどういう状況かは良く分かりません。

そしてこれらの選択肢の後には次の文が続いています。

If you think you have another acceptable use for the Advertising Identifier, contact us.

「もし他に許容される広告識別子の利用があると考えるのであれば、我々にご連絡下さい」とあります。

連絡すると個別対応が行われるんでしょうか。しかし「許容される利用用途の範囲」であり、特例措置が取られるわけではないと読み取れます。

iTunes Connect更新の真意

Advertising Identifierは広告のみに利用すること、取得した情報の利用範囲の定義については、Developerプログラムライセンスの利用規約の3.3.12項と3.3.13項で明文化されています。

ライセンスを保持している時点で、この規約を読んで同意しているはずで、今回新たに義務や契約が発生したということはないです。

ではなぜ更新が行われたのでしょう?

サードパーティライブラリであっても、アプリ開発者の責任になるよ 」という点に、明示的に同意を求めなければならないほど、状況が悲惨だったからでしょう。

開発者の責務

Advertising IdentifierはUDIDを代替するスーパークッキーを意図して設計されているため、利便性と危険性を伴います。用途が広告のみに限定されているのはそのためです。

実際のところ、開発者自身が用途の限られているAdvertising Identifierを取得するのは稀だと思います。組み込んだ広告SDKが利用しているケースがほとんどでしょう。

そのため、アプリ開発者はAdvertising Identifierが何に利用されているのか以前に、その存在そのものを知らないということがありえました。

今回、開発者に対して、取得の意図をチェックさせる変更を加えたのは、実質的にはAdvertising Identifierの存在と存在意義を開発者に対して周知し、そして サードパーティのポリシーを確認したことを表明させるため でしょう。

Advertising Identifierの十全性

「広告型追跡を制限」の尊重について、チェックボックスがあるのはなぜでしょう?

Advertising Identifierは実際のところ完璧ではないためです。

例えばユーザーがAdvertising Identifierをリセットしたとしても、以前のAdvertising Identifierと紐付けることで、半永続的に個人を一意特定することができますし、ユーザーが「広告型追跡を制限」の設定を有効にしたにも関わらず、これを無視して端末内部に保持したAdvertising Identifierを使い続けることが可能です。

もちろん、これらの行為は禁止されています。

しかし広告ライブラリはソース非公開のため、evilな動作を行っていないと証明することはできません。

これからはアプリ開発者は自身が利用しているブラックボックスなライブラリも含めて、Advertising Identifierを規則に沿って利用していることを誓約する必要があるのです。

で、結局どーなるのか。

先に述べた通り、「同意しなければリジェクトされるリスクを負う」だの「義務化され違反者は追放される」というような表現が正しいとは思えません。ライセンスを持った時点で既に義務は背負っているはずですし、サードパーティーの無理解に伴うリジェクトラッシュはむしろ峠を越えた頃じゃないでしょうか。

この確認事項の追加によって、開発者のこれまでの、「ライブラリのせいだから自分のコードは悪くない」という意識が変わり、コンバージョンだけでなく、セキュリティを尊重した広告SDKを選択しようという動きへ変わっていくのではないでしょうか。

一方、広告SDKを提供する側は、開発者に対してAdvertising Identifierの利用ポリシーを遵守していることを表明しなければなりません。evilな広告業者は淘汰され、コンシューマサイドから見ると平和に収束していくと考えられます。…やや楽観的過ぎるかもしれませんが。

もっともiTunes Connectの確認事項がそれほど重要視されていないのは、戦略物資である暗号について適当な解答をしてもどーにかなってる辺りでお察し下さい。外国為替及び外国貿易法とかEARとか気にしてる人、そんなにいないでしょう。