AppStore バイナリサイズ 調節備忘録

このところ概算2週にひとねたのペースでMASにコードを送り込んでいるんだけど、ん?と思うところがあったので、備忘。

 

ScalaHaskellで一度でも成立したモノ書いちゃうと、メンテナンシーやらテスト単位の取りやすさから、もうその辺の手続き型の言語は使え(いたく)なくなるわけで。。。

型を自由に作れない、OSなんかの環境とシステムバウンダリが明確に分けられていないなんちゃってHaskellとはいえ、なるべくpureにswift3なんぞで書いてます。

が、いざレビューが通ってMASに公開されたらバイナリサイズが思ってたより無茶苦茶でかい。

 

MASだからサイズのせいでdownloadが伸びない何てことはあまりないので、別に放置でも良かったのだけど、購入者の中には気にする人もいるので、今回対応してみました。

 

気づいたきっかけは、最近出したAppSummonerというwindowエクスプローラアプリなんだけど、最初はあまり気にせずv1.0をMASレビュー通して、公開されたらなんかでかい。。。アプリページのサイズ欄には22.2MBと。

 

気になるので、XcodeのArchiverからiTCに送ったアーカイブをローカルに吐かせてサイズを見ると。。。ほんまや。。。

 

管理単位明確化のために、MASにあげているスクショやらdescriptionやらもgitで管理していて、一緒にバイナリにほうり込まれているためそれら全部外して再コンパイルするも、19MB...

 

で、.appのContents以下を見てみると、Frameworks以下が一律8MB。そして、使ってもないCoreDataのdynlibなんかも同梱されている。。。launcharも本体も全く同じ構成だったので恐らくはテンプレ。

いや、各ライブラリ、ブリッジとか言うサイズの域を凌駕してるし、Framework設定の意味はどうした。

 

Xcode7ではそんなことなかったのだが、どうやらXcode8+swift3では、SwiftのdynlibをFrameworksに何でもかんでもぶち込むようになっているご様子。

ditributer側の立場に立つと、必要なlibだけフィルタかけるような細かい変則ルールを持ち込んで、iTCへ"動かんのじゃぁ"と山ほどクンロク入れられるよりは、何も考えず全部盛りにして、私のようなあほがポチポチボタン押しても、とにかく最低限動くものとしてビルドするように設計した方が、対応コストが安いという計算があるのだろう。

こうしておけば、確率的にはその中のサイズを気にするクラスタのみが文句を言ってくる、とりあえず動いてはいるので、損益補填とかいう話にもなりにくい。

確かに気持ちはわかる。。。わかるが!というお話。

 

今回Xcode8にあげたのと、login起動を仕込むには、app形式をもう1個Library/LoginItemsに内包しないといけないので、計算上ただしい。

つまり、私が書いたコードはカスほどに小さい。

まぁ、昔のフロッピーのCMよろしく、1MBで図書館一棟ぶんの文字情報を格納できるわけで、少々手が早くてもひとりで作ってて図書館以上の情報量を2週で生産できるわきゃないのだけど。

 

閑話休題

で、ここからが解決編、といえるかどうかはあやしいので、私はこうした編。

今回、いかんともしがたい課題が3点ほどある。

1. launchAtLogin機能を仕様上残すには、launcharアプリを内包させないといけない

    - つまり、ひとつのアプリなんだけどなかのひとは2人必要。

2,3年前はlaunchctl直接いじって突っ込めたんだけど、SANDBOXのせいでできなくなった。Appleとしては、疑わしいpermissionは全不許可ということだろう。

今回のappでもレビュー中、それroot扱いなの?ってところで一悶着あったし。

 

2. swiftコードのFrameworkはdynlibなので、app形式を取っている以上必ず手の届く所に置かないといけない

    - 同じモノを、各構成appでいちいち持たせないといけない。DRYではないが、なんらかそうせざるを得ない理由が出荷前にあったんだと思う。

 

 3. (これは個人的に) コンパイルオプションとかはなるべく変えたくない

    - コンパイルオプションはもはや人間が管理するもではないと思っているし、toolchainのdistributerはここを変えて配布する権利をがっちり握っているので、オッカムの剃刀的な考えから、いじるとしても必要最低限にとどめたい。

自分の間抜けさから、まぁことあるごとに忘れるし、忘れる可能性を秘めた工程は自動化するか、それができなきゃ排除(興味を持たないように)するポリシーでうちはやってます。

 

特にコンパイルオプションに関しては明後日の方の設定一つ変えられると、全く予期してなかった挙動を示す確率が非常に高い。 

これはむかーし学生時代、狂ったようにintelのブートストラップ書いてた頃のgas、gccから得た教訓。LLVMといっても、外部とのIFはほぼgccだし。

中間抽象化言語導入するなら、どっかで損切りして捨てればいいのにといつも思う。。。

 

 で、上記3条件のandを取ると、最も低コストな解決策は、launcharのswift捨てだとデザイナーとして今回は判断しました。

だって、login起動判断に必要なlaunchar-helper.appはAppDelegateとmain.storyboardしか持たないし、起き上がって判定かけたら直ぐterminateするカゲロウみたいな存在だから。

 

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
BOOL running = NO, active = NO;
NSString* mainAppBundleId = @"xxxx.xxxx.xxxx";
    NSArray* applications = [NSRunningApplication   runningApplicationsWithBundleIdentifier:mainAppBundleId];
    if (applications.count > 0) {
        NSRunningApplication* app = applications.firstObject;
        running = YES;
        active = app.isActive;
    }

    // Quit
    if (!running && !active) {
        NSString* scheme = @"xxxx://"; 
        NSURL* applicationURL = [[NSURL new] initWithString:scheme];
        [[NSWorkspace sharedWorkspace] openURL:applicationURL];
    }

    [NSApp terminate:nil];
}

これだけのために、いつ正常対応するか解らないundocumentedな仕様と、各社似て非なる仕様を持つダイナミックリンクライブラリの調査をする気にはなりませんわ。

何が起きているかと、対して何がしたいか、何ができるかがはっきりしてから、10分で終わった。

 

こういうときswift/ObjC両方書けるって便利。

あ、あとXPC書くにはまだObjC書けないと辛いか。

 

もう!functor(関手)が実装されていて、環境依存から解放されてこそのFPでしょうが。

誰でもいいから、この超環境依存FP早くなんとかしてくれぃ。