なるようになるかも

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

Java版Foundationのこと、覚えてる?

qiita.com

素朴な疑問があったので投稿してみました。これはその補足みたいなもの。

CoreData

CoreDataって凄く独特な世界観を持っていて扱いづらいと思った人が多いのではないでしょうか。

なんでこんなのがXCodeやInstrumentsのテンプレートに居座っているのか理解できない、関連張ると動作が重くなりすぎて使い物にならない、CoreData内部の良く分からないエラーに悩まされている、普通にSQL書きたいからFMDB使ってます、幻滅しましたRealmに移行します、そんな雰囲気だと思います。

CoreDataの原型は、NeXTのFoundationが用いられた最初の製品であるEnterprise Objects Frameworkです。

20年前にデータベース製品の学習コストの高さと、オブジェクト指向言語とのインピーダンスミスマッチの問題に取り組んだこの製品は、普及はしませんでしたが思想的な面などでは高い評価を得たそうです。AndroidのORMは栄枯盛衰でどれを使えばいいのか悩むのですが、iOSにはEOFの血脈を受け継いだCoreDataがいるのです。感動的ですね。

WebObjects

まぁCoreDataは本気でどうでもよくて、その原点たるEOFを発展させた世界初のWebアプリケーションフレームワークWebObjectsというNeXTの主力製品がありました。

これは「WWW経由でRDBMSからオブジェクトをフェッチし、HTMLにレンダリングする」という雰囲気の、こちらも概念としては面白いフレームワークで、主にエンタープライズ向けの製品だったので一般にはほとんど普及しなかったにも関わらず、コアなファンがいるとかいないとか。いないかも。

Apple内製では未だに現役みたいで、例えば今でもAppStoreはWebObjectsで動いています。

Java Foundation

WebObjectsもNeXT由来なのでObjective-Cで書かれていたのですが、Javaに完全移行した時代がありました。

全てをFoundationに依存していたWebObjectsが、Javaへ移行するために取った手段が、Foundationのクラス群をそのまま同名でJavaに再実装するという暴挙でした。

これは現存するJavaDocを読むと面白いです。中核技術だったKeyValueCodingの扱いとか、セレクタはReflectionだったりとか、カテゴリに相当する機能は実現できなかったりとか、ArrayListではなくNSArray使わなければならないとか。(KVOプロトコルのリファレンスに、「キー値監視はJava Applicationでは使えない」って注意書きがあるのもこの辺の名残だと思います、これいつ消えるんだろう…)

com.webobjects.foundation (WebObjects 5.4.2 Reference)

この前例を見る限り、「Foundationが徐々にSwiftのように洗練されていく」という可能性は低いかなーと思っています。

他言語に移植・移行するには、Foundationの持つ機能は、Objective-Cの言語仕様そのものすぎるのです。また、依存している多くのフレームワークが影響を受け互換性を失うデメリットと釣り合わないと思いますし、Swift型推論による安全な静的型付け言語であることを選択し、Smalltalk由来の動的型付けの機能を喪失したことも、移行を阻害する大きな要因になると思っています。

とはいえ

今からObjective-Cを書くのは半分くらいは技術的負債だと思っています。

KVCのせいで実質的にprivateなインスタンス変数は作れない、高速列挙プロトコルとか記憶から抹消した方がマシ、blocksにGCD、weakにnullableにGenericsと詰め込みすぎてもう意味が分からない言語になってます。

一方で、Core系のAPIを使うにはまだまだC互換言語の知識が捨てられません。

Swiftは当初、「Objective-C without the C」と大々的に宣伝されました。自分はこのフレーズにとても期待しましたが、Swiftでも関数ポインタが絡むAPIの辛さは軽減されないどころかむしろ酷くなっていて、特にクロージャを関数ポインタに逆変換する術がなくて詰むことが多いです。CFuntionPointerUnsafeMutablePointerで頑張るくらいなら普通にObjective-Cで書いた方がはるかにマシという感じです…。

とはいえSwift移行の大号令は近いうちにかかるでしょう。ただしそのとき殺す相手は、LLVM中間コードによるネイティブ並の動作を謳う、RubyMotionやXamarinのようなプロダクトなんじゃないかなぁ、その方がAppleらしさを感じます。

逆にSwiftがグダってくれれば、高級言語選択がもっと自由になるのかも。

Nexus5にAndorid M Preview入れた。

あるいはAndroidのデータ領域についての話。もしくはLollipopのバッテリーイーターに対する愚痴。

「Previewを入れるには全てのデータが消える覚悟が必要」みたいな話をよく聞きますが、Androidのデータ構造を理解せずに、batやシェルスクリプトを使ってアップデートをしている人の勘違いです。

前提条件はありますが、ユーザーデータ領域を残したままM Previewを入れることはできます。

Androidのデータ領域について

fastbootコマンドはおまじないではありません。データ領域の意味が分かれば、何をしているのか理解できます。

データ領域名 説明
bootloader fastbootモードで起動するのがこのブートローダ…みたいな認識で合ってるんでしょうか。通常oemロックにより読み取り専用領域となっていますが、Nexus端末はアンロックできます。ブートローダーのアンロック時には端末データが全部消し飛ぶので、一度アンロックしたら放置してます。
radio 通信領域に関わるファームウェア。「ベースバンド」という言葉が良く使われます。
boot boot領域です。Linuxカーネルと初期RAMディスクに相当します。bootとbootloaderはよく混同されてる気がする。
recovery リカバリ起動を行うための領域です。OSが起動不可能になった場合に、修復を行えるようにするため、OSとは別系統のシステムが存在するのです。必要最小限の構成のため、中身はほぼbootと同じらしいです。root化とカスタムリカバリのインストールもよく混同されてる気がする。
system Android OSのうちカーネルを除く部分(要はユーザーランド)です。システムはユーザー権限で動くので、root化するにはカスタムリカバリを用いてsuをインストールする方法が採られます。
userdata ユーザーデータ領域です。この領域を消さなければ、OSのみをアップデートすることができます。初回のOS起動時に既存のユーザーデータの最適化を行ってくれますが、挙動がおかしくなる場合もあるようです。その場合は諦めましょう。
cache 一時的なキャッシュとして利用される領域です。

flash-allがやっていること

fastbootにはいろいろコマンドがありますが、書き込みを行うflashと、削除を行うeraseだけ覚えとけば問題ない気がします?

flash-all.shの中身を見ると以下のような感じです。

fastboot flash bootloader bootloader-hammerhead-HHZ12h.img
fastboot reboot-bootloader
sleep 5
fastboot flash radio radio-hammerhead-M8974A-2.0.50.2.26.img
fastboot reboot-bootloader
sleep 5
fastboot -w update image-hammerhead-MPZ44Q.zip

まずブートローダーを書き換えて、続いてベースバンドバージョンを上げます。

fastboot -w updateというコマンドが出てきます。-wオプションは、userdatacacheeraseすることを意味します。その上でupdateコマンドで、zipファイルの中身をflashします。

image-hammerhead-MPZ44Q.zipの中身を見ると、

  • boot.img
  • recovery.img
  • system.img
  • userdata.img
  • cache.img

があります。それぞれが上表のデータ領域に相当しています。

ユーザーデータ領域を残したままAndroid Mに入れ替える

前提として、ブートローダーをアンロックしたまま利用している必要があります。ブートローダーのアンロックに伴う端末初期化は回避する方法がなさそうです。

fastboot flash bootloader bootloader-hammerhead-HHZ12h.img
fastboot reboot-bootloader
fastboot flash radio radio-hammerhead-M8974A-2.0.50.2.26.img
fastboot reboot-bootloader

ここまでは公式の方法と同じです。あとはimage-hammerhead-MPZ44Q.zipを解凍して、

fastboot flash boot boot.img
fastboot flash recovery recovery.img
fastboot flash system sytem.img

だけやれば、データを移行してOSだけアップデートできました。

cache領域のeraseは、やった方がよさそうですが、今のところやらなくても支障はありません。root権限があるなら、fastbootコマンドを使わなくても、単に/cacheの中身を消せばいいです。

Lollipopのバッテリー問題

Android Mはバッテリーの持ちがよくなる」みたいな話があります。アップデートした理由はほぼこれに尽きます。

条件が謎なのですが、Lollipopは一旦バッテリーイーターなモードに入ると、何もしなくても大体10~12時間くらいで電池が尽きるようになるのです。

f:id:quesera2:20150530163809p:plain:w300

頻繁に通信しそうなアプリは、Gmailを除いて消したのですがそれでもこれです。「その他」が電源をガリガリ削っています。同じ条件のiPhone 5Sは90%くらい残ってます。

当初、バッテリーがへたれたのかと思って新品に換えたけど意味がありませんでした。Nexus 5の電池交換はY字精密ドライバと、両面テープで張り付いてるバッテリーを剥がす根気が必要で、無駄に面倒です。

f:id:quesera2:20150530164955p:plain:w300

Android Mにして様子見てる感じでは上の通り真っ当な電池の減りです(Wifiのアイコンが塗りつぶされてるのはバグっぽい…iOS7で良く見たやつだ…)。バッテリー効率が改善されたというよりも、L Previewから放置されてきたバグをやっと直した、って感じがします。

AppCompatDelegateの話。

Toolbarは正直ないわーって感じなんですけど、皆様は適合されたんでしょうか。

DroidKaigiなるイベントがあったらしく、Toolbarが受け入れられるのかディスられるのかが気になっていたのですけど、「BaseActivityの是非」が一番のトピックだったっぽい?個人的には、IntentなどのAndroidのルールを隠蔽するために、

nextScreen(ActivityFactory.createFromId(GAMEN_ID_HOGE));

みたいなオレオレ画面遷移メソッドを実装しているBaseActivityは爆破したくなりますが、それ以外で不満を持ったことはない感じです。

さて、AppCompatDelegateの話です。ActionBarActivityは死んでAppCompatDelegateの時代が来ました。それをざっくり説明すると以下の通りです。

今までのサポートライブラリ

f:id:quesera2:20150504181917p:plain

FragmentActionBarの概念が導入されたのはAndroid3.0。このうちFragmentAPIレベル4まで、ActionBarAPIレベル7までバックポートされています。

Fragmentのバックポートの責務はFragmentActivityに、ActionBarのバックポートの責務はActionBarActivityにあったため、上のような継承構造となっていました。

どう変わったか

f:id:quesera2:20150504191246p:plain

AppComatDelegateActionBarのバックポートや、マテリアルデザインのための諸々の責務を全て委譲するように変わりました。

実際のところ、少し前からActionBarActivityDelegateという名前で、ほぼ現在と近い実装になっていました。22.1というマイナーアップデートでお披露目となっているのもそのためだと思っています。

AppCompatActivityは単なるガワです。継承しなくても互換性のあるアプリを作ることができます。

これまでの継承構造では、PreferencesActivityが死にクラスになっていたとしばしば批判されていました。AppCompatPreferenceActivityというサンプルを見るのが分かりやすいのですが、この変更によって、どのような継承構造のActivityであっても、AppComatDelegateに処理を委譲することで互換性を維持できるのです!!

というのは嘘です。

確かに公式ブログにも、

However, this wasn’t just a rename. In fact, the internal logic of AppCompat is now available via AppCompatDelegate - a class you can include in any Activity, hook up the appropriate lifecycle methods, and get the same consistent theming, color tinting, and more without requiring you to use AppCompatActivity (although that remains the easiest way to get started).


私訳:しかしながら、この変更は単なる改名ではありません。AppCompatの内部ロジックは全てAppCompatDelegateにより今すぐ利用可能です。このクラスはAppCompatActivityを必要とせず、あらゆるActivityに取り込むことができ、ライフサイクルメソッドと適切に連携することで、一貫性のあるテーマ、色の変異(color tinting)などを実現できます。(それを開始する最も簡単な方法として、AppCompatActivityも残されています)

というようなことが書いてあります。

しかし、AppCompatActivityFragmentActivityを継承していることを忘れないで下さい。

FragmentActivityが扱っているのはv4のFragment(偽)です。普通のActivityが扱うFragment(真) とは別物です。

AppComatDelegateが解決する問題はマテリアルデザインとActionBar(と便宜的に呼ばれ続けているToolbar)の領域だけなのです。そのため、独自のActivityに組み込んでもいいケースは、Fragmentを使う機会が全くないか、もしくはandroid.support.v4.app.Fragmentandroid.app.Fragmentが混在しても支障がない場合に限られるということです。同様のことはLoaderにも言えます。

AppCompatActivityは必ず継承した方が無難だと思います。

また、AppCompatDelegateが扱うToolbarはv7のToolbar(偽)であり、普通のActivityが扱うToolbar(真)とは別物なのも微妙なトラップなのでご注意下さい。

Androidのタスクとプロセスの話。

あるいは、「Androidのstatic変数は勝手にクリアされる」という迷信についての話。

タスク

Tasks and Back Stack | Android Developers

A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack (the back stack), in the order in which each activity is opened.

Androidでは、ユーザーが特定の作業を行うときに、相互作用を持つActivityの集まりをタスクと定義しています。これはバックスタックと呼ばれる、Activityを起動した順序で保持したスタック構造で管理されます。

タスクはいつ始まるのか

ホーム画面もまたActivityです。

  • タスクとはいつ始まるのか
  • タスクを管理しているのは何なのか

と疑問に思うはずです。

You can set up an activity as the entry point for a task by giving it an intent filter with "android.intent.action.MAIN" as the specified action and "android.intent.category.LAUNCHER" as the specified category.

タスクのエントリポイントとなるのは、お馴染みandroid.intent.action.MAINandroid.intent.category.LAUNCHEIntentです。ランチャーから起動されたActivityはタスクのルートになります。

そして、現在のタスクはActivityManagerによって管理されます。

Android4.Xの「最近使ったアプリ」に表示されるものがタスクだという認識でおおよそ問題ないはずです。

タスクはいつ終わるのか

If the user leaves a task for a long time, the system clears the task of all activities except the root activity. When the user returns to the task again, only the root activity is restored.

ユーザーが一定時間タスクを離れた場合、システムは自動的にタスクをクリアし、再びルートのActivityを起動します。

alwaysRetainTaskStateフラグを使うことで、この自動的にクリアする挙動を防げます。逆に、clearTaskOnLaunchフラグを使うことでランチャーからアプリを起動した場合に必ずタスクをクリアすることができます。

また押さえるべきポイントとして、タスクのクリアとキルは別物という点があります。「最近使ったアプリ」からタスクを消去した場合、そのアプリのプロセス(後述)も終了しますが、タスクがクリアされてもアプリのプロセスが保持される ことがあります。

タスクは複数のアプリケーションで構成される

タスクという概念が抽象化しているのは、その名の通り「ユーザーの作業」です。それは複数のアプリケーションで構成されることがあります。

例えば、写真を撮影し、それを編集し、メールで投稿する場合を考えてみると、

  • カメラアプリ
  • 画像加工アプリ
  • メーラーアプリ

の3つのアプリケーションのActivityを横断することがあります。

もし他アプリケーションの呼び出しが「ユーザーの一連の作業の一部」であるならば、単純にstartAcitvity()で呼び出します。

そうでない場合、他アプリケーションのタスクを明示的に開始することを示すためにFLAG_ACTIVITY_NEW_TASKのフラグを利用します。

プロセス

Processes and Threads | Android Developers

When an application component starts and the application does not have any other components running, the Android system starts a new Linux process for the application with a single thread of execution.

実行中の他のコンポーネントのない、アプリケーションのコンポートネントが起動された場合、Androidのシステムは単一の実行スレッドを持つLinuxプロセスを開始します。

タスクがActivityの集合であり、複数のアプリケーションを含めることができたのに対して、プロセスはアプリケーション単位で一つだけ存在します。

does not have any other components running という点も重要です。通常、実行中のアプリケーションのコンポーネントはプロセスを共有します。

複数のプロセスを持つアプリも作れますがそれはどうでもいいので省略。

Androidのstatic変数が消えるとき

最近のAndroid端末はスペックが良くなったのでこの現象に遭遇することもなくなりましたが、古い記事を見ると「良く分からないけどAndroidのstatic変数は勝手に消えるから使うべきではない」みたいな情報があります。

これは正しくは、「タスクは生きているが、プロセスが死んだ」状態で発生します。

他のアプリケーションがメモリに負荷をかけるなどした場合、Androidシステムは自動的に余計なプロセスをkillします。一方、ActivityManagerがタスクをクリアする条件は「ユーザーがタスクを離れてから一定時間経過した場合」なので、killされたプロセスのActivityを含む中途のタスクが、ActivityManagerに残るケースがあります。

このとき、タスクを再開するとそのアプリケーションのプロセスが新規に開始され、「static変数がクリアされた状態」となります。

解放されたstatic変数のオブジェクトにアクセスして、NullPointerExceptionというのが出来の悪いアプリでよくあるバグです。ほとんどの場合、原因はルートのActivityで初期化処理をやっているせいです。初期化処理は適切な場所で行いましょう。

よくある勘違い

「Activityを保持しない」で、メモリがない状況を再現できる

あのオプションはActivityを強制的にfinish()しているだけなので、アプリケーションのプロセス消失を再現することはできません。

タスクキラーを使えば、長時間放置した状態を再現できる

タスクキラーの種類にもよります。

プロセスを殺すタイプだとタスクも消去されます。メモリを大量に確保するタイプのものを使いましょう。

今年入ってから読んだ本とか。

NFC Hacks

NFC Hacks ―プロが教えるテクニック & ツール

NFC Hacks ―プロが教えるテクニック & ツール

2015年になって、ようやくNFCについて真面目に学びました。

iBeaconは流行した頃に触ったんですけど、NFCは「どうせiOSで使えないし~」という理由でスルーしていたのです。

NFCとiBeaconを同じカテゴリに入れてるところからして間違いでした。全然別物ですね。

この本はおおまかに、

  • 物理レイヤ・・・動作原理について、電磁誘導がどうとか
  • 規格レイヤ・・・typeごとのメモリ構造やコマンドコード
  • アプリケーションレベル・・・AndroidWindowsにおけるコード例

という広いレイヤでNFCを扱っています。

ただ、この本を漫然と読んでも、書いてあることの意味を理解できないと思います。また、以下の点に注意が必要です。

  • ページ的に仕方ないですが、コードは割と基本的なことしか書いていないです。また、出版時期の関係上、Android 4.4のHost card emulationのような比較的新しいAPIには触れていません。
  • 標準規格寄りで書かれています。
    • 例えば、MIFARE Ultralightntには、それ自体の仕様と、同時にそこから派生したType2という仕様があります。この本は個別のNFC製品の仕様ではなく、NFC Forumが定めるtypeを基に広く解説しています。
    • メッセージの保存方法として、NDEFを重点的に解説しています。これはAndroidで規格を意識せずNFCタグを扱う上で非常に便利です、スマートポスターを作るなど簡単な用途としては十分です。本格的にNFCを採用したアプリケーションを開発するのには向かないように思います。

APIがあって、データの保持規格に共通仕様があるなら、簡単に読み書きできるんでしょ?」という甘い考えはあっさり崩壊しました。例えば交通系カードの履歴を読み込みたければFeliCa仕様書を読んでコマンドコードを調べて、バイナリと格闘し、タグと通信する必要があります。

タグを買って実際にコードを書いて、そのとき副読書としてこの本を読むと、非常に的確な解説だと唸らされます。

ハイパフォーマンス ブラウザネットワーキング

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

英語版は無料公開されています

ブラウザネットワーキングとあるように、どちらかというとJavaScriptでフロントエンド最前線にいたり、インフラ系エンジニアに向けた本なのですが、「HTTP/1.1の何が通信のボトルネックになっていて、HTTP/2にすることでどう早くなるのか」について学ぶ上でこれを超える本はないと思います。

もし「SPDYに対応した通信ライブラリを使えば、それで勝手に高速になるんじゃないの?」とか思っているなら、とっとと読んだ方がいいです。

モバイルネットワークの歴史、どのようにネットワークを利用することが電力的に効率的なのかなどの章も必読です。原著はアメリカで採用された3G、4G規格を前提に話をしていますが、日本語版は「日本ではどうだったのか?」という部分をフォローしているので(それだけに限らず全体的に訳注が非常に丁寧)、日本語版がお奨めです。

Web SocketやWebRTCの章は流し読みしましたごめんなさい。ネイティブ開発の片手間のJavaScriptでやるには流行り廃りが激しすぎです…。

APIデザインの極意

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

APIデザインの極意 Java/NetBeansアーキテクト探究ノート

これは個人的な娯楽として読みました。

読み解くには知識と恐らく経験が必要で、しかも凄まじい密度があり、かなり人を選ぶ本だと思っています。少なくとも、私は後方互換性に厳密であるべきAPIを公開した経験がないので、正しく解釈できている自信がありません。

なので感想として言えることは、これほど面白い本が邦訳されてくれてありがたいです、ということくらいです。

挿絵のセンスはないと思います。

AndroidもiOSもC#で書けるようになればいいのに。

Xamarinはなんか生理的に受け付けないのでなしで。

しかし,実際に実装するにあたっても可読性のために同じようなところに改行を入れるとおもいます.

という記述が気になったので、実際に実装してみました。

Bookmarkの構造が分からないから、NSDatebookmarkedBOOLisHotentryを持つクラスだと仮定します。

- (Bookmark *)latestHotEntry:(NSArray *)bookmarks
{
    NSPredicate *latestHotEntry = [NSPredicate predicateWithFormat:@"SELF.isHotentry = YES AND SELF.bookmarked = SUBQUERY(%@, $b, $b.isHotentry = YES).@max.bookmarked", bookmarkes];
    return [[bookmarkes filteredArrayUsingPredicate:latestHotEntry] lastObject];
}

そんなに冗長な記述にならない気がします。

NSPredicateの公式ドキュメントの雑っぷりとか、@maxの存在なんてほとんど誰も覚えてないとかそういうのは考慮してないので、実用的に使えるかは微妙ですけど。

Swiftって実際にはNSObjectNSArrayの恩恵を受ける必要があるので、「純粋なSwift」そのものはいまのところ微妙だなーと思ってます。

あと、blocksはAppleによる「C言語」の拡張なので、blocksの可読性が悪いのを理由にObjective-CディスってSwift持ち上げてるのはなんか虚しい。しかも両方とも仕様作ったのはAppleですよ…。

参考資料

強制バージョンアップの話。

という記事を見かけたので。

このライブラリの実装の問題点

key description
type 基本的には強制バージョンアップを行うことを前提に解説していますが、SRGVersionUpdaterではキャンセルボタン付きの告知アラートを表示することも出来ます。強制アップデートの場合は"force" を任意でのアップデートの場合は"optional"を入力して下さい。

これ、設計ミスってません?

一度致命的なバグを出して"force"で通知したら、それ以降二度と"optional"は使えません。「必ず一定以上のバージョンを使って欲しいけど、最新版の通知もしたい」ようなユースケースに対応できないなら、"optional"の存在意義はない気がします。

あと、「評価が付くまで様子見ユーザー」層は毎回"optional"の通知を繰り返し見せられて離れます。開発者がいいと思ったアップデートが必ずしもユーザーに受け入れられるとは限らない(むしろ逆のケースのほうが多い)のです…。

もう一点指摘するなら、一度強制バージョンアップ対象になった端末は、その後通信する必要がありません。何度起動してもストアに飛ばされるだけなので。

条件を付けてレスポンスをキャッシュすることで、余計な通信負荷を減らすことができます。機内モードに入れるなどして、バージョンチェックを迂回する裏道もなくなります。ただし サーバーの設定が間違っていた 場合の救済策がなくなります。

解決すべき問題について

古いバージョンのアプリで特定の操作を行うと、データが破損しアプリの操作が不可能となるバグが発見された

この本質的な原因は、単なるテスト不足です。

強制的に最新のバージョンに上げてしまうと、新たなバージョンに致命的なバグがあったとき、全ユーザーに被害が拡大するだけです。

品質管理の問題に対して、「強制バージョンアップの運用で解決する」という方法論を取るのは、正直微妙な感じがします。

サーバーサイドとの通信部分で使う認証ロジックに問題があり、古いバージョンでの認証ロジックのままでは成り済ましが出来てしまう脆弱性が発見された

これも根本的なところではテスト不足なのですが、それに加えてアプリとの連携APIを作る場合は、

  • 利用不可(非推奨)バージョンであることを通知する
  • 利用非推奨APIとなったことを通知する
  • サービスの終了(移行)を通知する

というのを、設計段階で想定するべきだと考えています。

これはリクエスト時にバージョンを受け取って、何らかのレスポンスコードで判定を行えばいいだけなのでまったく複雑ではないです。

アプリとAPIの寿命は長いとは言えません。設計段階で死の概念を内包しなければならない、と考えています。

楽ではないJSONのデプロイ

「たった3行」で導入できますが、運用は大変です。

これまでの経験上、最新版のアプリを配信開始しiTunes Connectの状態がReady for Saleとなっていても実際にApp Storeに反映されるまでには1〜3時間程度の時間差がありました。

にもありますが、アプリをデプロイしても実際に反映されるまでのラグを考慮する必要があります。全てのユーザーが本当にダウンロード可能になったのか、定期的に確認する必要があります。

加えて、サーバー側のデータの更新作業はユーザーが多い時間にやるべきではありません。

これはアプリの規模感にもよります。もしJSONに不備があっても、すぐに直して挽回可能な規模であれば当てはまりませんが、基本的には深夜層に実施することになるでしょう。深夜まで待機して、ミスったら大クレーム…、そんな作業やりたくない…。

バグのないプログラムがないように、必ず一度はヒューマンエラーが発生します。例えば、iOSAndroidを並行して開発した場合、両者のバージョンは基本的に一致しません。入れ違えただけで大惨事です。

古い端末のユーザーの考慮

AppStore固有の問題として、最新のバージョンとして「iOS7以降でしか動かないアプリ」をデプロイした場合、iOS6未満のユーザーは古いAPKを取得できてしまう というものがあります。

参考: アップル、古い iOSデバイス向けに互換性のある旧アプリを提供 - Engadget Japanese

一度アップロードしたバイナリはAppleによって管理され、開発者は制御できません。闇です。

UPDATE:訂正。アプリケーションのバージョンをiCloudに表示しない(iCloud availability)の設定で、旧バージョンが新規にダウンロードされることは防げる、が正しいです。

個人の感想

  • バージョンチェックは必須、ただし付けるなら認証なり各APIごとにきっちりやるべき
  • 全てのユーザーを強制アップグレードさせて、最新のAPIしか使わせないという選択肢は魅力的だけど、歴史を積み重ねたアプリだと破綻する可能性が高い
  • 「最新=完璧」という前提でいるのは危険
  • 簡単かどうかは実装ではなく運用で決めた方が良い