iOS/AndroidのTips共有会potatotipsでiOSの実行時引数は思ってるより簡単ベンリに扱えるんだよって話をしました。
potatotips (iOS/Android開発Tips共有会) 第3回
↑ 起動時のオプション引数はiOSだと上図のようにXcodeのスキーマで指定します。
int main(int argc, char * argv[])
↑ そして普通のUnixプログラムと同様にmain関数のargcに引数の個数が、argvに文字列の配列で入ってきます。
でもGUIの無いコマンドラインのユーティリティプログラムならともかく、iOSアプリでargvをParseして何かするとか面倒なだけだと思っていませんか?
詳しいことはスライドに書いたので端的に言うと、ある規則にしたがうとこの引数は自動的にParseされてNSUserDefaultsに格納されます。
つまりアプリケーションのどこでも特定のキーで値を取得することができるようになります。
ある規則とは次の2つです。
- キーとなる値は`-`(ハイフン)で始まる
- 2語以上の値はクオートで囲む
-key1 value1 -key2 'foo bar'
↑ 例えば上のように書くと、NSUserDefaultsに"key1", "key2"にそれぞれ"value1", "foo bar"という値が入ります。
また、この挙動を利用して既存の値を一時的に上書きすることもできます。
-AppleLanguages (en)
↑ 上記のように起動時引数を指定すると一時的に英語環境で起動されるのはよく知られたデバッグのTipsですが、これはNSUserDefaultsの`AppleLanguages`というキーの値を上書きしているのでそのような挙動になります。
起動時引数の値はNSUserDefaultsに永続化されるわけではありません。あくまでも起動してから終了するまでの一時的なものです。
そのような挙動はNSUserDefaultsの値が「ドメイン」という階層を持っていて、起動時引数はNSArgumentDomainという階層に格納され、その階層が一番最初に検索されるので、同じキーがあれば最初にヒットするので結果的に上書きされたことになるという仕組みです。
そして、意外に知られていないと思われるのが、ArrayやDictionaryのデータ構造を指定できるということです。
例えばArrayは次のようになります。
-arrayArg '( "foo", "bar", "baz" )'
Dictionaryはこうです。
-dictArg '{ "foo" = "bar"; "baz" = "qux"; }'
記法でわかるとおり、プロパティリストの表現形式です。
NSUserDefaultsの永続化先はプロパティリストなので納得ですね。
ArrayとDictionaryの組み合わせで、もっと複雑なデータを渡すこともできます(だいぶややこしくなりますが)。
上記のプロパティリストのフォーマットは古い記法ですが、今どきのXML形式も使えます。
-xmlArg “<dict><key>foo</key><string>bar</string><key>baz</key><string>qux</string></dict>"
起動時のタイミングでXMLのParseがなされているとか、それは本当に必要なのかという気がしますがとにかくそういう仕組みです。
これで、なぜ下のように書くと英語環境になるのかということのトリックがわかります。
-AppleLanguages (en)
第一引数の`-AppleLanguages`はハイフンで始まっているのでNSUserDefaultsにAppleLanguagesというキーで格納されます。
第二引数の`(en)`はプロパティリストの配列です。
NSUserDefaultsにはあらかじめAppleLanguagesというキーで設定で指定した優先順位で言語の配列が格納されています。
NSLog(@"%@", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]); => { ... AppleLanguages = ( ja, en, fr, ... ); ... }
その値を上書きするために第一引数はハイフンで始まる必要があり、第二引数にカッコを付けるのは、もともとのAppleLanguagesの値が配列なので配列を渡す必要があり、配列のプロパティリストでの表現形式はカッコで囲む必要があるからです。