2016-11-21

macOS版xibロード関数

...なんか管理が大変だったので、Hatenaから黙って引っ越しました。

macOSでもxibからロードする関数を立てておきたかったのと、swift3対応のため備忘。
システム上、高頻度の利用シーンがあり、3まで来てある程度Appleの設計もこなれて来たので、ライブラリとして書いてみた。

いつも思うが、Appleはxibに対して冷めたい。
設計者からすれば、もうnibみたいなレガシーシステムはデベロッパから切りたいので、何も考えずstoryboard使えということだとは思うが。
じゃないと、initWithNibnameコンストラクタ廃止して、NSArrayにxibから生成されたNSViewつめてインスタンス返すようなわかりにくいパスだけ残すなんて設計にしないと思う。

ただ、storyboardだとカスタムviewを単体で定義できないし、programableにviewController上のviewにバインドしたいときとか、特定の場所に特殊なviewを出したりしたいときにはやっぱりどうしてもまだ使う。
storyboardの中では結局xib使ってるんだけどね。。。複雑になり過ぎてユーザに開放できないわということだろう。
ドキュメンターに定評のあるAppleも説明コストのほうがかさむと判断したのかもしれない。
好意的に見ると、xib使わなきゃできないようなアプリケーションは、仕様が複雑になりすぎてるか、やりたいことを見失ってるから気づけ—ということかも知れない。
確かにiOSでiPad proをメインユースにとでも想定していないならそうかも知れない。
小さい画面であれもこれもできますと凝るほど、ユーザには負担がかかるんだよという、非常にわかりにくいメッセージだと考えると個人的には非常に納得できる。

で、Apple Developerより先に引っかかったのがこれ。


Load a UIView from nib in Swift


これはiOS向けのレポートだけど、いくつか比較した中では一番妥当に感じたので、ここからぱくってみる。
iOS版は数年前にObjCで書いたんだけど、iOSとmacOS、上物エンジンはさすがに若干違うので一部書き換えてある


assertはvendorが扱いを変えたり、個人の実験考察に誤りがあって棄却が必要になったとき、対応できるよう入れてあるモノ
ちなみに、何度か遭遇したけど何らかの条件下ではunsafePointerの順番ひっくり返るんだよなぁ。。。。
原因となり得る要素が多すぎて確信できる答えはまだ出てないけど。
たいていのレポートはfirstで決め打ち取り出ししているけど、なにか記述していない前提があるんだと思う

また無駄な処理を。。。といわれそうだけど、世界中に配るモノの一部なので一応保険に保険を掛けた構造といことで。
道具箱にframeworkとして入れておいてもいいんだけど、Categoryみたいにimportで切り分けられない。。。
一発間に冗長クラスを介在させるとかも考えたけど、それはそれでめんどくさいのでprox実装にしました。
あと、private gistをblogに貼ったら、robotがどう振る舞うかの実験もかねて。


-- 以下おもいつきの余談 --
それにしても、autoLayoutは複雑なCG座標系の概念でさえ、いかにも設計せず実装できまっす!みたいな顔をしているし、それを使ったstoryboardはXML生成の最適化処理にバグを持ってる気がして大いに気にくわないんだけど、コードで全部書くとパラメータがOOのあおりで意識しないところで書き換えられるなんてことは少ない反面、表示位置確認くらいの簡単なデバッグなんかに時間がかかるというジレンマ。。。
storyboardなりxibなりを書き換えたあとは、DerrivedDataごとclean(Xcode: Opt + ⇧ + ⌘ + k)が癖になってしまった。


概念上でもレイヤで完全に分離することで、利用者へ出しているIFをコントロールしたい、xibはもうカプセルに囲い込んで、xibのIFはstoryboardを通してしか使えないようにしたい現れなのかと。
余計な仮定はオッカムの剃刀でそぎ落とせ、KISSの法則も同じようなことだけど鉄則なので解らないでもない。
興味を持って考えるべきことはなるべく最小セットにとどめないと、思わぬ方向から要求が来たときに致命的な制約がかかるので。

ただ、やったらめったらそげばいいというモノではなくて、一貫性をもって取捨選択していく。
この一貫性というのが設計の本質といっていい(と私は考える)。

誰かをディスるわけではないが、よくS/Wに何百人月も投入して失敗するケースと、研究室やらベンチャーの少人数で高い生産性みたいな話があるけど、実はこれ当たり前の話。。。

だって、
関わる人たちの考えていることや意見の一貫性は、関わる人数が増加傾向にあるほど複雑化して損なわれる確率が高いのは想像にたやすい。
逆に少人数で、輪講なんかで同じ主張の本を読み、生活コンテキストやらイデオロギーやらフィロソフィーがある程度そろっていると、それぞれ違った課題を抱えていても、最初に思いつく解決アプローチは皆同じだったり、細かい設計まで言わなくても納得できる品質レンジに収まってたりして一貫性は保たれやすい
から

後者は私の仮説込みだけど、Blooksが人月の神話でとうの昔に言ってたことの受け売り(個人的にこう解釈している)です。

失敗経験則ではたいてい矛盾する2つ以上の課題を同時に解決しようとしてるんだけど、不思議なことに課題分析に工数を使うべきとの意見だけはついぞ出てこず、そして倒れるというアンチパターン。。。なぜだろう。

2016-11-08

githubのバックアップ

次作のMASネタでは、ちょいとしたgistの管理を一部機能として企てているため、github api v3について調べてたついでにgithubのバックアップスクリプトを書いて、10.11動作検証兼、BackupマシンのMid 2010 MBP機で毎7600secごとに走らせているのだけど、手元で私的に走らせるだけではもったいないのでgistに置いてみた。


 


最後のほう読めば解るけど、定期的にrepoを見て、backupサーバに展開されてなければclone、あればfetchしてlocalはなるべく最新に保つ。


そこまでやっておけば、もし別環境から新しくrepoを立てたり、forkしたとしてもローカルにとっておいてくれるし、macなのでHDDに落としておけばTimeMachineにお願いできる。


 


加えて全issueとそのときの全ラベルをprojectごとにjsonで取っておく。


私的にはissueってただの申し送り、もしくはtodo管理をするものではなく、knowhowを最もリアルに動くコーディング作業に即して書き留められるノートなので、open/close関係なくこれが全部吹っ飛ぶと思うとぞっとする。


もちろんprivate gistにsnnipetsとして書き留めるように心がけてはいるけど、どのコンテキストでどう動くかまではgistではちょっと辛いので、調査結果として動くコードをまるごと何がどう動いているのか取っておきたい。


 


残課題は、


issueに即したcommitの紐付けまではまだ取れていないのと、csv化。


個人的にcsvつまりエクセルやらシートでissueをみたいと思ったことがないのでcsvはやらないかも。


ただ、ネットに転がってるコードをいくつか見てて、各issueをcsvとかに変換するなら、webApiから持ってきたjsonをいきなりcsvライブラリで書き換えるより一旦jsonをオリジナルとして持ってて、別目的としてコンバートコードを噛ますほうが好みなので、この方針で行くなら今回のコードは論理的にどちらにせよ必要になる。


csvもしくはエクセル変換はそのうち気が向いたら(何かしら価値をみいだせたら)書き加えるかも知れない


(python, ruby, CPANあたりだと、色付けたりグラフ書いたりかなりゴリゴリと直でエクセルいじれるんで)


できればcsv等似非データに書き換えるのではなく、waffle.ioみたくオリジナルを都度興味の対象だけ読み替えて、エクセルIFのみを提供するような形に持って行きたい。


 


ちなみに下記pythonコードには元があるのだけど、v3で全然走らなかったのと、関数の分け方に難があった、目的がjsonのcsv化だったため99%書き直した。クラス化されていないのはその名残りです。



 


あ!これだけあればなんとかなりそうというところまでは取ってあるけど、リストアのリハーサルはしてないや。。。やだなー怖いなー


とりあえず目的はknowhowの実践テストといつまたどこぞの愚か者がIoT向けDNSにDDOSかけても自分だけは大丈夫という精神安定剤ですね。


 


 


default引数

ぱくりと言われようと、便利なモノは便利なわけで。


swiftもPythonと同じようにdefault引数設定ができるわけだけど、Appleのドキュメントにはあまり明示的にこう使えと書いていないので、備忘。


 


まず、Pythonでもswiftでも私がdefault引数をよく使うシーンは、リファクタリングの時。


例えば、ある関数が外部とのIFとして立てられていたとする。



internal func hoge( ) {....



 ただ、今この瞬間から似て非なる新仕様をぶち込まれ(みたくなっ)て、hogeに引数を待たせたバージョンがあると、全体設計として非常に管理しやすくなるじゃんという状況になったとしよう。


 


Javaなんかだったらこうする。



// テストが通っているのでこれは凍結


internal func hoge( ) {....} 


 


// 新IFとして、新規コードからはこれを呼ぶ


internal func hoge(abc : [Abc]) {...



一般にコンパイラはシグニチャで関数宣言を見分けるから。


これはswiftでもPythonでも同じようにできたのでサブセットして道具箱に入れておくといいかもしれない。


 


ただしこれが、デフォルト引数を持っている言語だとこう表現できる



 internal func hoge(_ abc: [Abc] = []) {


    if abc.isEmpty {


        // 昔のhoge()を一文字違えず埋め込むか、sub関数管理にする


        ....


    } else {


        // 新規コードからのIFとして引数版のhoge処理


    }



 Java版もそうだけど、これらの何が嬉しいのかというと、既にコード中にアホほど存在するcaller hoge()はびた一文書き換えなくてすむし、挙動変化が管理しやすい。あと従来関数と機能のfusionがしやすい。


 


なおJava版のほうも決して悪いわけではない。


ファイル長に寛大で、関数単位でバージョン管理するのがしっくりくるようなチームならむしろJava版の方が合っているでしょう。こっちもfusionするならhoge(abc:...)の中で条件によってhoge()を呼べばいい。


要はwrapperで統一IFにする。


 


私的になるたけ1ファイルは低ノイズで主張点だけに絞ってざっと読み込みたいほうなのと、Python流が苦ではないので後者のほうが好きというだけの話。


大事なのは、十分テストされたといえる既存コードは可能な限り保存して、新規コードとは分離できるところ。これがOOPの基本嬉しいこと。


なんなら引数に関数を放り込むIFにして、default nilにしておいてもいい。


 


こうするのがややこしくて、少人数で作ってて、連絡コストがそう高くないなら、hogeをnewHogeと改名する方向でもいいかも



// internal func hoge ...


available(*, deprecated: X.x, message: "ツカウナキケン")


private func hoge() {...}


 


internal func newHoge(....



こちらの方が、新IFからは確実に使えなくなり境界がはっきりするので、状況によっては一貫性を保ってテキスト全置換でいっそ変えてしまった方が理想。


コンパイラは関数名なんざ数列にしか見えてないので。


 


ただし文字ヅラに騙されて変なミス入れ込むのが人間。これが現実。


特に私は間抜けなので、よく全置換をやって、かつコメントも何も残してないもんだから、何時間もうんうん唸って(泣きながら)ふて寝したあとに、そういえばそうだった!とさもエウレカを得たような気分になって正常軌道に戻すことはあるけれど。。。でもそれって気をつけていれば一切支払わなくていいコストだったわけで、結局損失なのでよほどじゃない限り避けるようにはしている。


 


 ちなみに、availableに頼るときはprojectで一貫したwarning controlがされていて、意識的にwarningの見分けが付く状況でないと意味はない。


砂丘の砂粒ほどwarningが存在する中で、"ツカウナキケン"を意識して見わけるのはもはや拷問。


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

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


 


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


型を自由に作れない、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早くなんとかしてくれぃ。


Pass the MAS review!

東京にいながら、すっかりSF時間で生活しておりますが。。。


さっきiTCからArduinoネタアプリをMac App Store readyにしたよと連絡が。


 


いやー、SMC叩いてるところを、MAS向けに局所的に値のフェッチ処理書き換えて一か八かで出してみたら。。。reject一回だけ、しかもただアプリ名の文字列がiTCに申請してるやつとちょい違うから直してねというだけでした。


これで1日無駄にしたわけだからHIGの読み落としは反省すべきだけど、思っていたよりははるかにすんなりパスしたので一安心。


 


おそらく今回の勝因はArduino側で暴走してもmacOSには影響ないデザインにしたよという主張と、回路組まなくても、こうこうこういう状態になればパスしてるというテストケースをレビュアに示したところかと勝手に思ってます。


 


AFAIK, MASに上がってる中ではArduino作成補助ツールはいくつかあっても、回路を作れ!というものはないので、これってちょっとだけでも販路をこじ開けたと自分では思ってるんだけど。。。伝わらないだろうなぁ。。。細かすぎて。


これがもう押しても引いてもダメなら、退路はgumroadしかなかったわけで。。。


でもgumroadの客はむっちゃ反応悪いし、商品として響いてる感全然ないし、陳列棚間違えたか。やっぱり身元調査も何もない超お気軽クラウドファンドだった。どうしよう。。。と思ってた所でした。


でも出すと宣言はしてしまったので、これからちょっと寝て、9/15 UTCのローンチに向け、MAS版にちょっと手を加えたSandboxでは触れないSMCチップ制御版出すんだけど。


 


といっても、値のコール&レスポンス経路をXPC内で秘密のMAS経路からSMCに向けるだけだから、テスト含めてhalf mandayくらい?ここはちゃんと設計したんで。まぁたいした工数ではない。。。はず。


 


ちなみに、MASのレビュー基準若干変えます宣言から、App名にはちょっと厳しく接するようになったので、これから審査に出す人は注意してください。


「むかしだしたAppではんなこと言ったことなかったじゃん、実際僕のアプリで名前ずれてるのあるし」ってレビュアに反論したら、MASもApp数爆増してるからそんなこと言わないでよだって。


Quitラベルとインストール名、About、とiTC申請名はきっちり見てるみたいです。


ちなみのちなみに、テクニック的には、レビュア相手にごねても得はないので、情報を引き出すための主張はしても、主張しつつも曲げてもいいところはさっさと曲げちゃいましょう。


ぼくもこのときは、反論をレビュアに申し送りつつ、同時に改修版のバイナリに差し替えました。じゃないと、またラリーに1日かかるんで。。。


やっぱり、どんなに速攻差し替えても、次の審査が廻ってくるまでには1日あけるみたいです。たとえ担当レビュアが、「ここだけ直したら直ぐ再審査するよん」と言ってきたとしても信じちゃダメなところ。実際にあったし。。。


 


とりあえず商品ページのDNSがMASで浸透したら👉のリンク足します。


設計書とかコンセプト説明はここでっす。MASのdescriptionにだらだら書いても誰も読まんので、画にしました。


 


算数 小技 -- Item追加算術平均

個人的な道具箱に入っているちょっとした変形式の話。

もちろん拙作アプリに仕込んであって、このおかげで(これだけではないけど)計算ちょっぱやというモノ。

物理的なノートに書き留めた山ほどあるうちのひとつなんだけど、権利関係関係せずそれほど難しくもないところから漏らしていこうかと。

今回の備忘は、算術平均を算出したいんだけど、CoreDataからフェッチして平均値取り直すなんてことはしたくない。

でも中央値ではちょっとって時に、新規データと過去算出された算術平均値とアイテム数があれば全アイテム舐めるのとと同値の算術平均値が出せるという式の書き置き。

もちろん場合によっては、中央値を使った方が目的にfitしていることもあるが、今回は単純に{ \displaystyle I_k }の各個性がつぶれてもいいので、代表値を測定したいというもの。今回私の目的(要求)には算術平均がfitした。

よく言われる、ひとりでもビルゲイツが混じった母集合で平均年収を算術平均で取っても、説明変数として説得力がないというのは今回は別の方法で回避してある

see.

Cocoaで統計処理 四分位数実装編 - つらつらと日々のメモを公開するブログ

。

具体的なシーンとしては、ガンガンログが飛んでくる中、とにかく1Stepでも短く全てを表現する代表値だけメモリ上でもDBの1レコードにでも書き留めておきたいといったところか。

まず基本中の基本からいくと、算術平均とは何か。これは小学校で習うやつ。

定義としては、実直に全アイテム{ \displaystyle I_1 ... I_n }の合計値をnで割った値が全I{ \displaystyle I_k }を代表する値として、利用できるという概念。

つまり丁寧に書き下すと

{ \displaystyle<br/>\overline{I_{n}} = \frac{\sum_{k=1}^{n} I_k}{n}<br/>}

となる

じゃぁ、これに{ \displaystyle  I_{n+1}, I_{n+2}....}が鬼のようにむっちゃ飛んできたらどうすべきか?

災害レベル狼くらいなら、毎回全Itemを配列へフェッチしてぐるぐる回してもいいだろう。

アプローチとしてはbgスレッドに任せてしまうと言うのもありだろう。

ただ、ここで一つ考えてみて欲しい。これもし、ある程度大きなデータが割と速い速度で n -> +Infと考えたら、出荷時のちょっとしたデータなら平気で処理しきれても、そのうちスタックする。

配布された瞬間から、crashレポート受け取り準備万端にしておいた方がいい。

いや、お前はIndexingも知らん素人か、というならさらに考えてみて欲しい。

一般的なIndexとは、所謂SQLエンジンからのデータ管理の方法を代えて、検索速度を確保するアプローチだ。

普通はバイナリサーチができるようにツリー化したり、さらにキャッシュアルゴリズムを導入したりする。

そう。ツリー化するということは、データ構造が複雑になる。複雑ってことは登録に少なからず手間がいるってことやん、手間ってことは、ある程度のステップ数が登録に余計に必要ってことやん。余計に必要ってことは、鬼レベルでこのデータ登録は高コストやん。

この仕様で具体値はいらないということはないだろう。だって、具体値はDBから引っ張るんでしょ?...これもそのうち詰みます。

しかも、macOSのCoreDataはデフォルトでテンプレ組み上げると、XMLファイルでデータ振り回すので、indexという概念があるのかすら怪しい。



ということで、前振りはこのくらいにして、いきなりだが式変形してみよう。

つまり{ \displaystyle \overline{I_n}}に突如{ \displaystyle I_{n+1}}が飛んでくる。

このとき、{ \displaystyle \overline{I_{n+1}}}を算出したい。

だとすると、欲しい式は{ \displaystyle \overline{I_{n+1}}}

つまり

{ \displaystyle<br/>\overline{I_{n+1}} = \frac{\sum_{k=1}^{n+1} I_k}{ (n + 1)}<br/>}

を算出するのに、すでに解っているのは、一つ前の平均値{ \displaystyle \overline{I_n}}、とn

そして、今さっき登録されたログ{ \displaystyle  I_{n+1}}

{ \displaystyle \overline{I_n}}に何か足して、{ \displaystyle \overline{I_{n+1}}}が出せないものか。

これができるとクソ遅いディスクアクセスやシーケンシャル操作はなく、CPUの算術オペレータとレジスタ、一次メモリ上だけで完結する。はず。

積極的にこうしたい・・・としよう

こういうとき私は、まずなるべく小さな具体例から考える。つまりH/Wの解析同様足場を固めて次へ進む。

ここでは、[A, B, C]という数値があって、A, Bの平均にCが飛んできたらどうしようと課題設定してみる。

ということで、たとえば (1 + 2) / 2 = 1.5 に5が飛んでくる。このときなんとかして、(1 + 2 + 5)/3 = 2.67という答えを導きたい。

{ \displaystyle \frac{A + B + C}{3 ( = N + 1)} = \frac{A + B}{2 ( = N)} + X, a.k.a 2.67 = 1.5 + X }

となるXなので、割と簡単に

{ \displaystyle X = \frac{A + B + C}{N + 1} - \frac{A + B}{N}}

つまり

{ \displaystyle X = \frac{1}{N + 1} (C - \frac{A + B}{N})} とでてくる。

具体値で言うと、{ \displaystyle X = \frac{1}{2 + 1} (5 - \frac{1 + 2}{2}) = 1.17}なので、これを1.5に足すと、ちゃんと2.67になった!

ではこれを一般化してみよう。この作業がないと現実コードには使えない。

ここで、{ \displaystyle \overline{I_n} = \frac{A + B}{N}, \overline{I_{n + 1}} = \frac{A + B + C}{N + 1} }なので、

個人的なテクニック的には、数の大小は見ない振りして、ガンガン抽象変数で代替していくといいと思う。

と、

{ \displaystyle<br/>X = \frac{1}{n + 1} (I_{n+1} - \overline{I_n}) }

ここで、

{ \displaystyle<br/>\overline{I_{n+1}} = X + \overline{I_{n}}}だったので、

{ \displaystyle<br/>\overline{I_{n+1}} = \overline{I_n} +  \frac{1}{n + 1} (I_{n+1} - \overline{I_n})<br/>}

となる。つまり毎回、既にメモリ上にあるいっこ前までの平均値にXを足し続ければ、算術処理のみで平均値が出せる。

状況にも依りそうだけど、NSExpressionやら、NSPredicateで平均値だして比較してみたけど、こっちのが早いみたい。

とくにPredicateはあまり期待しない方がいい。そもそもDBというのは算術処理するものではないので。

おかげで拙作では気軽にテストできる数十万レコードくらいまでだったらスレッド気にせずへいちゃらで処理しきる

この式、GPLみたいなノリでFeedbackは欲しいけど、とくに権利は主張しないです。



Swift 小技 -- Keychainアクセス

ググるとKeychain関連の話は山ほど出てくる。中でも良くまとまっているのはこの記事。

Cocoaの日々: [iOS] Keychain Services とは

原理説明的にも、手順書としても詳細まで非常に良くまとまっているので個人的にエバノで管理しているくらいだし、実際拙作アプリもこれを参考に作った。ObjCなら何ら問題はない。



ただ、swiftでは一工夫いるのねというのが、今日の備忘。

厳密にはKeychainそのもではなく、UnsafePointerとかCOpaquePointerが主題。

 ググり倒して、最初に書いたkeychainのload関数はこんな感じ


    class func load(key: String) -> NSData? {

        let query = [

            kSecClass as String       : kSecClassGenericPassword,

            kSecAttrAccount as String : key,

            kSecReturnData as String  : kCFBooleanTrue,

            kSecMatchLimit as String  : kSecMatchLimitOne ]

        var dataTypeRef :AnyObject?

        let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)

        

        if status == noErr {

            let p = UnsafeMutablePointer<CFTypeRef>.alloc(sizeof(CFTypeRef))

            return NSData(bytes: p, length: sizeofValue(p))

        } else {

            return nil

        }

    }


 サイトによっては、UnsafeMutablePointer<CFType>ではなく、Unmanaged<AnyObject>になっている。

ただ、 Xcode ⌘ + クリックでsecurity内、SecItemCopyMatchingのswift宣言を見ると、


public func SecItemCopyMatching(query: CFDictionary, _ result: UnsafeMutablePointer<AnyObject?>) -> OSStatus


 となっている。

ありゃ?見た記事が古かったか。

と思って、UnsafeMutablePointerからallocでゴリゴリ取得しているのが、最初のテストコード。

これは、もちろんコンパイル通るし、なんかうまく動いてる風の振る舞いもする。

よく言われる、Keychain 常にnilを返す問題も、コンパイルオプションで最適化をNoneにすれば問題はなさそう。。。だが、なぜか初回起動のみ、必ずnil返しというバグに見舞われた。



フロー上は初回起動時には必ずNSUserDefaultのextensionに保存した設定値と、UserDefaultの顔して、実はKeychainという値(パスワードとか)もチェックしてセットアップするのだけど、全くもってうまくいかない。

途中でパスワード書き換えるテストは通る。なぜなら初回起動は考慮しないから。でも、デバッグ設定パネルからKeychain全消去して、起動しなおすと必ずパスワードだけ\0 * 8なんておかしな値を持って、期待しないルートを通って初期化されている。。。



もう、原因究明に数時間費やしてしまっていたので、手段としてObjCブリッジにするか、KeychainやめてAESでブロック暗号かけてやろうかと本気で思ったくらいのところで次の記事を見つけた。

うーん、つまりこうか!


    class func load(key: String) -> NSData? {

   ...

        var extractedData: AnyObject?

        let status: OSStatus = SecItemCopyMatching(query, &extractedData)  

        guard status == noErr else { return nil }

        let retrievedData: NSData? = extractedData as? NSData

        return retrievedData

    }


guardの使い方とダウンキャストは荒いけど、期待通りに動いている気はする。

そして、考えていたより超簡単。何よりswiftっぽい。

もう!OS、コンパイラ屋なら過去に一度でも決めた仕様はバックコンパチちゃんとして!



 ちなみにこう書くと、最適化オプション付けても問題無いみたいでした(今のところ)ので、思い切ってこうしてやりました!

f:id:setminami:20160802221313p:plain





ただし、まだ絶賛テスト中なので、これ見た人安易に信じないでください。あくまでAYORで採用のほどを。

あと、Mac App Store向けではないので、大前提としてcapabilityはkeychainのみでSANDBOX外してあります。そもそもsudoやらチップのレジスタやら複数スレッドからパラ(からの〜XPCでdaemon経由)で 一気にひっぱたいて廻るようなアプリなのでMAS向けアプリ制作の参考にはならんです。

ちなみに、Xcode 7.3.1での話。 









ふぅ、目的は何を置いても、動くことなので最終的に期待通り動けばそれでいいんだけど、いつもの調子で宣言追っかけたらLLVMのObjCブリッジ生煮え箇所にうっかり巻き込まれ、問題解決の妨げになったというお話でした。そういやPKDの小説に"知識は汝の身を滅ぼす"的なセンテンスがあったなぁ。聖書の引用らしいけど。



満足解の探索アルゴリズムは消去法に持ち込まれた瞬間、ただの手数合戦になるから嫌い!

Swift 小技 -- Regexと再帰関数 1

なんだかswiftで超長い文字列をRegexかけたら超めんどくさいことになってしまったのと、それに対するソリューションが打ち出せたのでそのメモ。


 


Regexのネタはsystemstatsのサマリーで、例えばこんな文字列がはき出される


 




Summary
=======
System Version: 15G31
Total Time: 91:23:15


Usage
=====
Wake Time: 67:14:38
User Active: 47:57:26


 ... 中略 ...


Memory Summary
==============
Swap Dev: SSD
Total: 8192.0 MB
Free: 297.3 MB
Wired: 1835.0 MB
Compressor: 1140.6 MB
Compressed: 3275.0 MB


...


Ranked Memory Activity
======================
Time: 2016-07-22 03:46:44 to 2016-07-22 03:56:45 (00:10:01)
Free: 791.1 MB
Wired: 1738.0 MB
Compressor: 1099.2 MB
Compressed: 3421.0 MB


...



で、やりたいことはこれから集計結果をKVSにして、CoreDataに保存。


階層構造を持っているので、[Key:AnyObject]にしてleafならVはDoubleなり独自型なりの具体値、leafでなくなんらか上位概念なら具体値にぶつかるまで[Key:[Key:AnyObject]]とする。


 


今日言及したいのは、その前段としては上記データ解析のための文字列引き抜き。


-Dオプションをsystemstatsにわたすと、JSONっぽいデータ構造になるんで、"} -> }:"とか部分的に何カ所か変換すればJSONにシリアライズできるかとも考えたが、それでは結局何度も飛んでくる同じデータを自前でサマってやらないといけないので、今回は演算をケチるためこちらを使うこととした。


 


DataRegulatorのプロトコルをつくって具体的Regulatorは必ずparseを実装する。


それぞれ具体的な処理対象はフォーマットがいちいち違ったりするので、Enumで実装して、興味のある処理対象にぶつかったらenum値が知っている処理内容(ここでRegex使う)を返すというそんな感じの設計。


 


最初に描いたのはこんな感じ。



func parse(data:String) -> [String:AnyObject] {
let exp = Type.MemorySize()
let memQnt = "\(exp.pattern)"

let timeForm = "\\d+:\\d+:\\d+"
let dateForm = "\\d{4}-\\d{2}-\\d{2}\\s\(timeForm)"
switch self {
// case Summary:
// case Usage:
// case CPUSummary: break
// case ThermalSummary: break
// case FanSummary: break
// case IOSummary: break
// case RankedIOActivity: break
// case AirportSummary: break
// case TopFanActivity: break
case MemorySummary:
let args = ["Swap Dev", "Total", "Free", "Wired", "Compressor", "Compressed", "Purgeable", "Internal", "External", "IOAccelResident", "IOAccelWired", "IOAccelDirty",
"IOAccelCached", "IOAccelPurgeable", "Faults", "Purges", "Zero-fills", "Reactivations", "Page-ins", "Page-outs", "Decompressions", "Compressions", "Swap-ins", "Swap-outs"]
let pattern = "\\s*Swap\\sDev:\\t(\\w+)\n\\s*Total:\\t\\s*(\(memQnt))\n\\s*Free:\\t\\s*(\(memQnt))\n\\s*Wired:\\t\\s*(\(memQnt))\n" +
"\\s*Compressor:\\t\\s*(\(memQnt))\n\\s*Compressed:\\t\\s*(\(memQnt))\n\\s*Purgeable:\\t\\s*(\(memQnt))\n\\s*Internal:\\t\\s*(\(memQnt))\n" +
"\\s*External:\\t\\s*(\(memQnt))\n\n\\s*IOAccelResident:\\t\\s*(\(memQnt))\n\\s*IOAccelWired:\\t\\s*(\(memQnt))\n\\s*IOAccelDirty:\\t\\s*(\(memQnt))\n" +
"\\s*IOAccelCached:\\t\\s*(\(memQnt))\n\\s*IOAccelPurgeable:\\t\\s*(\(memQnt))\n\n\\s*Faults:\\t\\s*(\\d+)\n\\s*Purges:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Zero-fills:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Reactivations:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Page-ins:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Page-outs:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Decompressions:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Compressions:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n" +
"\\s*Swap-ins:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)\n\\s*Swap-outs:\\t\\s*(\\d+)\\s\\(\\s*(\(memQnt))\\/s\\)"

return generateSimpleArgs(args,
                                             data: data,
                                              pattern: pattern)



自戒のためだが、自分で言うのも何だけどバカも休み休みにしろといった内容。。。もはやこのpatternはメンテナンス不能(正確には"したくない")だし、同じことを何度も何度も何度も書きまくり。


 


で、次にこう書いた。



case RankedMemoryActivity:


     typealias RegexType = (labels: [String], types: [Type], regex: String)


            


     let memory:RegexType = (labels:[""], types:[._MemSize], regex:"\\t\\s+(\(memQnt))\n")


     let memAct:RegexType = (labels:["Size", "Speed"], types:[._Int, ._MemSize], regex:"\\t\\s+(\\d+)\\s\\(\\s+(\(memQnt)\\/s)\\)\n")


     // CONTRACT: ()の数とtreeの配列サイズが一致すること subが空の時は1つしかないのでlabelをキー名とする


     // CONTRACT: item0のタプルが最上位キーとなる


     let args:[(label:String, regex:RegexType)] =


                [("Time:", (["Start", "Duration"], [._Date, ._Time], "\\t(\(dateForm))\\sto\\s\(dateForm)\\s\\*1\\)\n")),


                 ("Free:", memory), ("Wired:", memory), ("Compressor:", memory), ("Compressed:", memory),("Purgeable:", memory), ("Internal:", memory),


                 ("External:", memory),


                 ("IOAccelResident:", memory), ("IOAccelWired:", memory),("IOAccelDirty:", memory),("IOAccelCached:", memory), ("IOAccelPurgeable:", memory),


                 ("Faults:", ([""], [._Int], "\\t\\s+(\\d+)")),("Purges:", memAct), ("Zero-fills:", memAct), ("Reactivations:", memAct), ("Page-ins:", memAct), ("Page-outs:", memAct),


                 ("Decompressions:", memAct), ("Compressions:", memAct), ("Swap-ins:", memAct), ("Swap-outs:", memAct)]


            


     let comb = {(a1:String, a2:(label: String, regex: RegexType)) -> String in


                              return a1 + "\\s*\(a2.label)\(a2.regex.regex)"


                        }      


      let pattern = args[13 ... (args.count - 1)].reduce(


                args[8 ... 12].reduce(


                    args[1 ... 7].reduce(


                        comb("", args[0]), combine: comb) + "\n", combine: comb) +


                                   "\n", combine: comb)


      debugPrint(pattern)


      return generateComplexArgs(args,


                                                      data: data,


                                                       pattern: pattern)


 



 ミソは前から順にaccとしているので、reduce順は逆ですよといったところ。


なお糖衣が効いてるだけで、ObjCでもblocksと関数ポインタが解っていれば同じことはできる。


 


元来僕は、書くのに数十分かけた再帰文は読むのにも数十分かかると考える方なので、あまり技巧的なことはしないのだけど、さすがにこれは。。。


systemstatsの書式も、いまいちマナーに決まりが薄いし。


 


型はgenericsを使うという手もありそうだけど、どうもScalaにくらべてswiftは型のハンドリングがいまいちな感想。つまり、genericsに対応遅れ感がどうしても残る。


 


まだ完成している言語とは言えないので、しかたないとして、今抱えてる悩みは、Scala、HaskellのノリでガシガシFP構文書くと、Xcodeのソースコードwatcherプロセスがいい感じに死ぬこと。


 


*1:\(timeForm



拙作 Smoker Insight! is Out

f:id:setminami:20160627002853p:plain


拙作アプリがローンチされました。


👉  にあるように、CoreDataとデータ構造に興味を持って設計を起こしたため、ベースがcoreData(実態はsqlite3)の割にはデータ量が少々蓄積してもリアルタイム演算は割と速い方だと思います。


以前にもネタにしましたが、ミソはパーセンタイルと4分位数あとKVS少々。逐次検索を一旦KVS構造に読み替えるシステムなんで、一旦準備が整えばフラグ調整や、新たな喫煙データが加わることで全データの平均値が変わって各データのプロパティ変更が必要になっても1,2,3qをピボットにして高速追従します。


これを軸にシステム全体のデータハンドリングが組まれているため、よくあるユーザに見えてるアイテムについてだけの実行ではなく、毎回データ全舐めをして、全データ系内で整合性を保ちながらも十分な速度で動きます。


 このアプリ、任意のタイミングでの全データグラフ化機能を搭載しているので、これは外せない必要仕様なわけです。


遅延評価であれもこれも必要になったとき計算、ではいざというときユーザーを待たせてしまいますから。


 IAPに至っては、かなりJBに対して挑戦的な実装になっていて、途中で仕様が複雑になりすぎたため、ここだけは真面目にUMLを書いて設計しました。


これも機会があればまた詳しくネタとして展開します。


ただ、今の実装はappleidのログインダイアログがしつこく出るので、少し仕様変更入れるかも。


 


とりあえず、いまはまだまだ全然宣伝できてないので、向こう2週間は個人開発者向け宣伝サイトに順次レビューを寄稿しつつ、AdMob, iTCとにらめっこの予定です。


 


swift解体新書 その2 Enum RawValue

Swift.org - Documentationを読んでて、swiftのEnumがJavaなみに変態仕様になっていたのはチェック済みだったんだけど、いろいろ隠れ仕様があるみたいなのでそのメモ。


 


まずは手始めにこんなのを書いてみた


 


気になっていたのが、The Swift Programming Language (Swift 2.2): Enumerationsの次の記述。



Implicitly Assigned Raw Values


When you’re working with enumerations that store integer or string raw values, you don’t have to explicitly assign a raw value for each case. When you don’t, Swift will automatically assign the values for you.


 



 つまり、明確な型を指定しなくても、swiftcで良きに計らってなにかしらのサインとして、型付けするよと。


これの何が嬉しいかというと、Planet_whoAreYouみたいにrawValueほったらかしていても、switch-case文書くときには何も気にしなくていい。


とにかく.MercuryはPlanet_whoAreYouのMercuryというサインでしかないと。


これは、全てをオブジェクトで表現しようというOOP上正しい。


 


そもそも、K&R時代のenum値でアーキテクチャのWORD型を割り当てていたのは、Cという抽象表現から機械語に翻訳する上でそうするほか無かったからだという解釈もできるわけで。


本当なら、コーダーはenum値が具体的にIntで1だろうが、Stringで"Mercury"だろうがそんなな気にせず、Planet_whoAreYou.Mercuryという抽象的なモノとして扱えと。


こうしておくことで、具体値が何かの都合で突如IntになろうがStringになろうがDoubleになろうが.Mercuryは.Mercuryという概念でしかなくなり、概念上のシステムバウンダリから向こう側はそっから先で勝手に考えてくれれば、それで必十条件を満たすので、みんな幸せということにできる。


とはいえ具体値は気になるということで、それを確認しているのが各for内の



    print(x)


    // -> Earth


    let strA = String(x)


    print(strA)


    // -> Mercury


    print("\(x) = \(x.rawValue)")


    // -> Jupiter = 5


 



のあたり。どうやら2.2時点ではrawValue関係なく、Stringで内部に持っているらしい。正確には、String型でインスタンスを生成できるといったところか。


 


ただし、Planet_...で次のようにはできない。



swtch x {


    case "Mercury" : .....



"型と値はちっげーよ、勘違いすんなカス"と怒られる。


 


あと、



let y = x as! String



とかで強制キャストすると、Warningで"それ...やってもいいけどダメだと思うよ。その型xと関係ねーし。わけわからん!"といわれる。


で、実際実行してみるとInstructionエラー。機械語に展開したときに変なオペコードでも吐いてるのか?これはx86だからかも知れない。ARMでやったらnilスルーするのかも。 


 


でも、



swtch String(x) {


    case "Mercury" : .....



はPlanet_...全てで通るし、想定通りに実行される。


 


Stringの生成に関して、ニーモニック上は手前で実装されたmethodの呼び出しに続いて、"__...NSString_class"なんていかにもバックコンパチな外部シンボルを呼んでいるにもかかわらず、"...fixStringForCoreDataP11objc_object"なんて、名前を見る限り不穏なローカルルーチンに何度も何度もオフセットかけて正気の沙汰とは思えないくらいぐっるぐる飛び倒す。これ...CStringの残党引っ張りだすためなんじゃね?とすら思える。


ちなみにこの解析アプローチは、レジスタの書き換えを肉眼では追い切れず、もはや人間の読むフローではないので、あきらめた。


 


 


じゃぁということで、ひとつ疑問が生じる。"enum Planet_String: String..."って結局内部保持してるから二重に宣言するだけ無駄じゃね?Stringじゃないほうがお得かもって。。。


なんとなくここから次のソリューションをひらめく人もいるかもしれない。


例えば、 rawValueはIntだろうが、DoubleだろうがswitchのネタをString()インスタンスにしてしまえば、IntとString両方の値が持てるのでは?


つまり、caseラベルは"Mercury"などenum値を文字列表現したもの、rawValueはInt型だとすると、



func next_prev(x:Planet_Int, isNext:Bool) -> Planet_Int! {


    switch String(x) {


    case "Jupiter":


        if isNext {


            return Planet_Int(rawValue: (x.rawValue + 1))


        } else {


            return Planet_Int(rawValue: (x.rawValue-1))


        }


    // something else ....


    default:


        return Planet_Int(rawValue: 1)


    }


}



なんて、このコードは例としては良くないが、要はStringでcase分岐かけつつ、Int値演算で次or前のenum値を得ることができる。そして実際実行可能だ。


皆enumにclassを内包させたりして結構苦労する.Neptuneの次は.Mercuryみたいに、循環enumも割と簡単にできてしまうし、なんなら、regexやら遅延評価で超技巧的なこともできそうだ。


 


しかし、これ実はOSやコンパイラ配布側(つまりApple)からすると相当筋の悪いソリューションで、思いついて"自分天才ですから"なんて言ってちゃいけない(と思う、個人的には)。


理由は上記で出てきた、



...Planet_whoAreYou.Mercuryという抽象的なモノとして扱え ...



 というのがその全て。


これ、抽象的に扱うというのは、具象的な扱いは全て配布側で握るよ、気が向いたら内部表現なんて全然違うモノにするよん。といっている(ようにも聞こえる)。


つまり、ある日突然xをStringでインスタンス化したコードは全く想定外の動きを示し、実は人間の読めないハッシュ値になることすら否定はできない。例えば、swift3ではenumが直で階層構造持てるようになって、.Jupiter.NaturalSAT.Ganimede, .Mercury.None, .Earth.MTSAT.HimawariVIIIとか(妄想)。


今の仕様で、これをやろうとすると結構めんどくさい。主張点がぼけるからかもしれないが、Appleが出しているトランプの例示が、別々に宣言したenumの組み合わせでしかできないことを示しているのかと。確かにpropertyは委譲できないけど、関数やらstruct/class宣言は内包できるんで、がんばればできるのかとは思う。


 


なんかいろいろ議論が混線しちゃったけど、言いたいことは、enumにこんな特徴見つけたが、たとえお客から数時間後に緊急納品ねじ込まれたとしても、よい子はマネしちゃだめということ。


いや、たいていはチーム内の誰かがアーキの意見を聞かず、色気を出して技巧に走り、自分の守備範囲ではないEnum値をIntで順序ありきとかに勝手に書き換えてシステム前提ぶっ壊す。あげく褒め殺してアピールをしてくるというのが現実なんだけど...


だからJavaなんかは、禁則コードが描きやすくなってるんだよね。Appleはその辺あんまり興味ないみたいだけど。


"やられて困ることは、はなからできなくしろ" これ鉄則。


マーフィーの法則の失敗する余地を残したシステムは、いつか誰かが必ずやらかすってやつ。


Swift解体新書 その1 ニーモニック確認環境

通常ならswift de classDrivenの続きのはずだが、一旦中断してクロスコンパイル環境から解体することにした。


 


なぜなら、WWDC2015 - 408にむっちゃ恐ろしいことが発表されていたから...


忙しさに負けて、チェック抜け...間抜け


 


詳しい解説はネット上和訳が出回っているのでここでは割愛するが、これによるとAppleは(少なくともswiftのPLであろうabraham氏によれば)、



継承構造は継承と言いつつ資源配分問題になると、


途端に厄介な問題を抱え込むので、そこは正式にはサポらんよ。


Protocol-Orientedで書いてね、てへぺろ



 と、いろいろ誤解を招く訳しかたをしたが、言いたいことはclass/struct/enumで継承使って親クラスの資源を無考で使えると思うなよ、そもそもabstractみたいな概念自体わしゃ好かん。


そんな細かいこというやつは、protocol+extension(これは明言してないけどObjCならprotocol+Category)で記述しろと。。。


 


これつまり、Appleはダブルチェックロッキングとかめんどくさいこと考えたくないから、独自路線を行きます。JavaやらC++と同じ概念で継承を考えると細かい所で違うものになるから痛い目見るよと(独自解釈)。


Alan Kay先輩とBjarne Stroustrup先輩の視点差に関して、ObjCでは業界トレンドに任せてごまかしていたことをついに認めたコメントなんだと思う(独自解釈)。


 


 


ということで、基本に立ち返る。


言われてみれば、swiftcも1.1時代にobjCとたいして変わらんことしか確認せず、まだ具体的には触ったことがなかったのでちょっと触ってみた。


最終Goalはニーモニックでクラス階層構造を確認すること。


この記事のGoalはとりあえず.o, .sファイル相当の内容確認ができるところまで。


 


---


閑話休題。では、以下本日のゴールに向かって順番に。


まずはswiftc  これがないとはじまらない。


これはxcodeなり、swift.orgからなりtoolchain取ってくれば入っているはず。



$ swiftc -v


Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)


Target: x86_64-apple-macosx10.9


 



 素知らぬ顔して、clangがLLVMのフロントにあることを告白している。


前に見たときこんなの出力してたっけ? そんななわかりきってるので、今日は触れない。


 


次、binutils これは、swiftc -SなりxcodeのDebug Workflow > Dissassembly でも代用はできるけど、サマるんだったら入れておいて損はない。


macならbrewで入れればまず問題は無いだろう。EL-CapitanのSIP問題に引っかかったら、ググって自己解決してください。


Linuxならapt-get


ここではgobjdumpが使えれば必要十分。


 


解析ツールはそろったので、次それぞれの使い方に進みます。


swiftc


これは、コンパイルするのに使う。


とりあえずの試料としてざざっとLoopテストを書いてみた。


asの確認をするのであれば平でfuncを書いた方が分かりやすいが、とりあえず。。。あとprobe効果なんかもほぼ考慮していないので、試料内容に関する突っ込みはご容赦いただきたい。


もちろん配列がL2に入るかなんて、とりあえず数回して誤差詰めとけ位にしか考えていない。


 



 


 Playgroundで書き出したが、XcodeのREPLの起こし方がアホで、編集の度にインタプリタ実行をかけ直そうとする。Loop試験のような何百万もループ回すコードだとさすがに固まるため、途中でコピペしてAtomでやった。


たぶんなんらかXcode設定はあるかと思うが、どちらにせよREPLだとs/n比低すぎてLoopの試験にはならんので、結局コンパイルする予定だからあまりこの場では深く考えないものとする。


あくまでGoalはニーモニック確認


 


 で、この試料をswiftcで実行ファイルにビルドする。



$ swiftc -O -sdk $(xcrun --show-sdk-path --sdk macosx) -o aStruct.out for-inExp-struct.swift



どうやらエントリポイント(main)はfoundationにリンクすることで、バイパスしているらしい。普通はこれで走るわけはないので注意。


 


今どきはアルゴリズムが成熟しているため、-Oを付けた方がより実測値に近づくがこれはお好み。 


--sdkはiphoneos(全小文字)にすればiOS向けにクロスできるけど、objdumpが欲しいだけなので、x86でいいだろう。


試料でUIKitではなくFoundationをimportしているのはそういう理由。


 



$ aStruct.out



でターミナルなんかから直接実行されるファイルができあがる。


もろもろ面倒なので、script書いた



 


OUTPUTはこんな感じ



StructTrials


opened for : av = 59.826675, total = 5982.667542


closed for : av = 26.785226, total = 2678.522583


for-in : av = 27.640303, total = 2764.030273


zipIndexing : av = 61.732862, total = 6173.286194


while : av = 59.986423, total = 5998.642334



zip、whileはなんとなく想像ついたけど、開区間(...) 閉区間(..<)でこんな違うんだ。


zip同様Loopごとに余計な命令群でも挟まってるんだろう。この辺は、dumpoファイルを元に解析予定なので後述(たぶん)


Pythonがrange使ってる理由がなんか解る気がする。


 


AppleDevにはfor-inで書けとしか書いてなかったんで、swiftでは実質高速列挙なんだろうな。ObjCではenumerate...blocksと挙動が違ったような覚えあるけど。


ほんとはfor(;;)も比較対象に入れたかったけど、syntaxに無かったようなので断念。


この辺の考察も想像の羽を広げてる場合ではないので、後述。


 


gobjdump


gnuなのでgas, gcc同様、頭にgが付く。



gobjdump -S -d aStruct.out > objStruct.txt



こんな短いコードだけど、FPテクを使ってるからか全貌はかなりの長さ。


以下、callqで飛んでいる興味のあるところだけだらだらコピペする



0000000100002090 <__TFV7aStruct7runTest11letUsLoopinfT_T_>:
100002090: push %rbp
100002091: mov %rsp,%rbp
100002094: push %r15
100002096: push %r14
100002098: push %rbx
100002099: push %rax
10000209a: mov %rdi,%rbx


// reset()
10000209d: movq $0x0,0x4b10(%rip) # 100006bb8 <__Tv7aStruct5totalSi>
1000020a4: 00 00 00 00


// self.myFunc() のはず
1000020a8: callq 100005314 <__ZL20fixStringForCoreDataP11objc_object+0xc9>
....



__ZL20fixStringForCoreDataP11objc_objectは0x10000524bとあるので、offset0x9cで


0x100005314へ。



100005314: jmpq   *0xdee(%rip)        # 100006108 <__ZL17_load_method_type+0x267> 



 



0000000100006108 <__DATA.__la_symbol_ptr>:
100006108: in (%dx),%al 


...



うーーん、anonymous関数ポインタのハンドリング...で、これ以上読み進める自信が無いので、すこしFP の手を緩める。


FPでは値も処理も区別しないため、何段階かアプローチはあるのだけれど、MAXひよってこうした。


 



もはや、関数ポインタを即値で代入しているだけで、何がしたいのかよくわからないが、おかげでdumpファイルが100K近く小さくなってくれた。


つまり固定値ラベルを追えばいいだけに。


 


いや、目的を考えたら、かっこつけずに初手からこうすべき話なのは解っていたのだが。。。FP+OOP脳に邪魔された。


 


stack操作はノイズだとして、各こういった対応になる


closed for -> av = 26.785226


for i in 0 ..< testValues.count {
        total += testValues[i]
} 


 


0000000100002200 <__TF7aStruct6test01FT_T_>:


....


....


10000220b: mov 0x10(%rax),%rcx


10000220f: test %rcx,%rcx


100002212: je 100002235 <__TF7aStruct6test01FT_T_+0x35>


100002214: mov 0x499d(%rip),%rdx # 100006bb8 <__Tv7aStruct5totalSi>


10000221b: add $0x20,%rax


10000221f: nop


100002220: add (%rax),%rdx


100002223: mov %rdx,0x498e(%rip) # 100006bb8 <__Tv7aStruct5totalSi>


10000222a: jo 100002237 <__TF7aStruct6test01FT_T_+0x37>


10000222c: add $0x8,%rax


100002230: dec %rcx


100002233: jne 100002220 <__TF7aStruct6test01FT_T_+0x20>


100002235: pop %rbp


100002236: retq 


100002237: ud2 


100002239: nopl 0x0(%rax)



 オーバーフローでオペコード例外とか。


インデクスはrcxに最大値取ってからdecしてるけど、一般的な条件分岐jmpらしい。全てがレジスタで完結しているし、妥当な最適化がされているから速い。


 


open for ->  av = 59.826675


let max = testValues.count - 1
for i in 0...max {
        total += testValues[i]
}


0000000100002240 <__TF7aStruct6test02FT_T_>:
....
100002244: mov 0x4965(%rip),%rax # 100006bb0 <__Tv7aStruct10testValuesGSaSi_>
10000224b: mov 0x10(%rax),%rcx
10000224f: test %rcx,%rcx
100002252: je 100002287 <__TF7aStruct6test02FT_T_+0x47>
100002254: dec %rcx
100002257: xor %edx,%edx
100002259: nopl 0x0(%rax)
100002260: mov 0x4951(%rip),%rsi # 100006bb8 <__Tv7aStruct5totalSi>
100002267: add 0x20(%rax,%rdx,8),%rsi
10000226c: mov %rsi,0x4945(%rip) # 100006bb8 <__Tv7aStruct5totalSi>
100002273: jo 100002283 <__TF7aStruct6test02FT_T_+0x43>
100002275: cmp %rdx,%rcx
100002278: je 100002285 <__TF7aStruct6test02FT_T_+0x45>
10000227a: inc %rdx
10000227d: cmp 0x10(%rax),%rdx
100002281: jb 100002260 <__TF7aStruct6test02FT_T_+0x20>
100002283: ud2
100002285: pop %rbp
100002286: retq
100002287: ud2
100002289: nopl 0x0(%rax)



このレベルだと、letによる参照透過性の違いは見られない。 


そして、cmp %rdx, rcxあたり怪しすぎる。rcxとrdx二本ふりまわしてdec,inc繰り返してるのか。


retqの前にud2が来てるのもなんかおかしい気もするが、こういうものだっけ?


-Oつけてもこれだと言うことは、..<はなんらか展開しくってるように思える。


実行速度も出てないし、開区間指定はまだこなれていないのかもしれない。


そもそも、for文は昔から0からはじまる閉区間なので、需要があるまではあまり力入れてないのかも


 


for-in  -> av = 27.640303


for v in testValues {
        total += v
}


0000000100002290 <__TF7aStruct6test03FT_T_>:
....
100002294: mov 0x4915(%rip),%rax # 100006bb0 <__Tv7aStruct10testValuesGSaSi_>
10000229b: mov 0x10(%rax),%rcx
10000229f: test %rcx,%rcx
1000022a2: je 1000022c5 <__TF7aStruct6test03FT_T_+0x35>
1000022a4: mov 0x490d(%rip),%rdx # 100006bb8 <__Tv7aStruct5totalSi>
1000022ab: add $0x20,%rax
1000022af: nop
1000022b0: add (%rax),%rdx
1000022b3: mov %rdx,0x48fe(%rip) # 100006bb8 <__Tv7aStruct5totalSi>
1000022ba: jo 1000022c7 <__TF7aStruct6test03FT_T_+0x37>
1000022bc: add $0x8,%rax
1000022c0: dec %rcx
1000022c3: jne 1000022b0 <__TF7aStruct6test03FT_T_+0x20>
1000022c5: pop %rbp
1000022c6: retq
1000022c7: ud2
1000022c9: nopl 0x0(%rax)



あーなるほど。testValuesの次アイテムを取り出すのに、raxをアドレスぶんオフセットかけてるのか。 closed forと極めて似ている。処理時間も近似している。


 


while -> av = 59.986423

var test = testValues.count - 1
while test > 0 {
        total += testValues[test]
        test -= 1
} 

<__TF7aStruct6test04FT_T_>:
1000022d0: push %rbp
1000022d1: mov %rsp,%rbp
1000022d4: mov 0x48d5(%rip),%rax # 100006bb0 <__Tv7aStruct10testValuesGSaSi_>
1000022db: mov 0x10(%rax),%rcx
1000022df: cmp $0x2,%rcx
1000022e3: jb 100002317 <__TF7aStruct6test04FT_T_+0x47>
1000022e5: test %rcx,%rcx
1000022e8: je 100002315 <__TF7aStruct6test04FT_T_+0x45>
1000022ea: dec %rcx
1000022ed: nopl (%rax)
1000022f0: mov 0x48c1(%rip),%rdx # 100006bb8 <__Tv7aStruct5totalSi>
1000022f7: add 0x20(%rax,%rcx,8),%rdx
1000022fc: mov %rdx,0x48b5(%rip) # 100006bb8 <__Tv7aStruct5totalSi>
100002303: jo 100002315 <__TF7aStruct6test04FT_T_+0x45>
100002305: dec %rcx
100002308: jo 100002315 <__TF7aStruct6test04FT_T_+0x45>
10000230a: test %rcx,%rcx
10000230d: jle 100002317 <__TF7aStruct6test04FT_T_+0x47>
10000230f: cmp 0x10(%rax),%rcx
100002313: jb 1000022f0 <__TF7aStruct6test04FT_T_+0x20>
100002315: ud2
100002317: pop %rbp
100002318: retq
100002319: nopl 0x0(%rax)
100002320: push %rbp
100002321: mov %rsp,%rbp
100002324: push %r14
100002326: push %rbx
100002327: mov 0x45d2(%rip),%rbx # 100006900 <__ZL12demangleLock+0x40>
10000232e: test %rbx,%rbx
100002331: jne 1000023b9 <__TF7aStruct6test04FT_T_+0xe9>
100002337: mov $0x20,%edi
10000233c: mov $0x7,%esi
100002341: callq 1000054b8 <__ZL20fixStringForCoreDataP11objc_object+0x26d>
100002346: mov %rax,%rbx
100002349: mov 0x3cd8(%rip),%rax # 100006028 <__ZL17_load_method_type+0x187>
100002350: mov %rax,(%rbx)
100002353: mov 0x4496(%rip),%rax # 1000067f0 <__TMLFT_T_>
10000235a: test %rax,%rax
10000235d: jne 10000237e <__TF7aStruct6test04FT_T_+0xae>
10000235f: mov 0x3cda(%rip),%rsi # 100006040 <__ZL17_load_method_type+0x19f>
100002366: add $0x8,%rsi
10000236a: mov $0x1,%edi
10000236f: mov %rsi,%rdx
100002372: callq 100005488 <__ZL20fixStringForCoreDataP11objc_object+0x23d>
100002377: mov %rax,0x4472(%rip) # 1000067f0 <__TMLFT_T_>
10000237e: mov %rax,0x8(%rbx)
100002382: movq 0x3ca6(%rip),%xmm0 # 100006030 <__ZL17_load_method_type+0x18f>
100002389: pshufd $0x44,%xmm0,%xmm0
10000238f: movdqu %xmm0,0x10(%rbx)
100002394: xor %eax,%eax
100002396: lock cmpxchg %rbx,0x4561(%rip) # 100006900 <__ZL12demangleLock+0x40>
10000239d: mov %rax,%r14
1000023a2: je 1000023b9 <__TF7aStruct6test04FT_T_+0xe9>
1000023a4: mov $0x20,%esi
1000023a9: mov $0x7,%edx
1000023ae: mov %rbx,%rdi
1000023b1: callq 1000054be <__ZL20fixStringForCoreDataP11objc_object+0x273>
1000023b6: mov %r14,%rbx
1000023b9: mov %rbx,%rax
1000023bc: pop %rbx
1000023bd: pop %r14
1000023bf: pop %rbp
1000023c0: retq



やめてくれ!もはや見る気がしない。zipはfuncに立てるのを忘れたが、なんか皆察しの通りかと。


 


objdumpからニーモニック人力評価のための環境構築というGoalは満たせたし、


長くなりすぎて何がしたいのかが不明瞭になってきたので、今日はこの辺にしておこう。


 


 気が向いたら次は今回大幅に日和ったラムダ式の追い方か、いきなり核心を突いて継承とprotocol+extesionの違いを見てみよう。


swift de classDriven その1 virtual関数と具象実装

どんなにFPを極めようと、結局中でやってることはblocksなので、FP恩恵の一部しか受けられない、FPの何が嬉しいかが解ってればまずObjC + blocksで書き下せる、Cocoaが結局ObjC目線なので、エンジニアは何かしらCocoa + ObjCとのコンパチで軋轢を被る、おかげでかっこつけて実装した参照透過性すらだいなし。なにやってんだか。

とはいえApple様はもうアランケイってだれ?状態で、罪をNextとObjCなんならPARCにもかぶせて捨て去りたくて仕方ない、そして最も厄介なことにswiftは教育含め、特にwanabe相手に金になる。

 

そんな、言語仕様界のあばれはっちゃく、swiftに時間を見つけて着手していこうかと。

ObjCのときは、OOPなら当然実装されているべき仕様が、変な小技使わないとできなかったり、結構はまりポイントだったので、その辺から。

 

いやはや、FPを知ってればOOPの代わりになると勘違いしてる輩が多いけど、ScalaやらOcamlやら、今OOPとFP組み合わせのパラダイムシフト中だから。

OOP極めてないと、いくらFP学んでも何の恩恵も得られないよ?というメッセージをこめつつ、とりあえず、初回はFBで先行で出した記事2件のコピペ

 


2016/4/16 晴れ


swiftで継承構造とabstractメソッド的なことやってみた。


例えばクソほどボタンが必要なときに、実装さえあるなしに応じて1メソッドで分岐する。


あれば、具象実装末端まで落ちていく


なければ、default実装へ。


default実装が存在するので、厳密にはstroustrupのvirtualなんだけど。


CallerClassでassert(sender.action())かかるよう、UITestで全叩きさせるとリリースストッパにできていいかも。
なお、Javaがなぜファイルで明確にひとつずつの処理単位(actor)を分けられるようになっていて、それの何が嬉しいとして言語設計されているのかよくわかんない人にはお勧めしない。

多段継承したとき、ObjCみたいにKVSで自前管理しなくていいぶん素直かな?
結局実体はKVS管理なんだとは思うけど。。。


 


 


 


2016/4/17 曇り


swiftで...その2
多少ググってswift流ならどうやるか調べてみた版。
こっちの方が、abstっぽくはなったが。。。
ちがいは、まだ構築中で未実装関数にPCが当たったときに、@noreturn関数内で完全停止するか、何らかdefault処理が入るか。
言うなれば、その1はvirtual(C++)、その2がabstract(Java a.k.a C++ pure virtual)といったところ。


好みと言うだけだけど、出荷後にやっちまったことが発覚したときのFS性から考えるとvirtualの方だろうなぁ。


 


Releaseで関数ごと入れ換えればいいだけなので、リリース、テスト、デバッグの各コードを疎に保てる。


 


テスト中に失敗検出のため、わざと落とすのと、デリバリ後にユーザの手元で落っこちるのとでは目的が全然違うからなぁ。


僕のインチキ設計哲学では、目的に合わせて極小影響粒度でオブジェクトやら処理やらを入れ換えるというのが好み。


abstは実装段階で忘れず全部梱包しろよってことだと解釈してるので。


 


個人的には、virtualでもabstractでもあまり興味は無く、言語設計つまりOSとの親和性として、どちらがCocoa設計に即しているかだけなんだけど。


 


swift blogみてもそういう所は出てこないんだよな。


 


 


Cocoaで統計処理 四分位数実装編

Objective-C, 仕様検討, 個人的見解, 思案中


元OS屋から見て普通に考えるとCocoaでは演算高速化のため、NSExpressionによる統計関数を用意している(のだと思う)


なぜなら、開発サイドから見ればそうすることにより



  • 開発者が独自で変な実装しだす確率を下げる (テストしてないところをがんがんほじくり返されるから。何が起きるか解らないため、提供者としてはむっちゃいや。特に演算処理は高速化を容赦なく求められるところなので、開発者は1命令でも削りたくてうずうずしている)



  • 自分たちの都合に合わせて、実装を自由に変えてリリースできる (当時ああは言ったけど、経営者の気が変わったときとか、頭のおかしいエンジニアが新製品や論文の売り文句に騙されて昨日と全く違った実装をこっそり入れてリリースしたくなったとか。)

  • なんかややこしいH/W仕様を使いたくなったらベンダー最適化し放題  (リリース時は時間に追われ、仕様もよくわかんなかったけど、よくよく見たらむっちゃ便利機能が見つかった。よくある。本当に。)


なんて戦略が取れるから。


しかしググった感じ、iOSではNSExpression#expressionForFunction@”median:”は使えない?なんてレポートもちらほら…いや、中央値出せなくてpercentile出すのはめんどくさい。なにより統計コードの天敵は計算ミス。


 


中央値算出くらいなら奇数サイズなら配列の真ん中、偶数サイズなら中央2項の算術平均取るだけなので、簡単でバグも入りにくいから自分で書いてもいいけど、sciPyが使えれば演算速度以外のほぼ全ての問題は解決するので、iOSへのPython組み込みとのProsVSConsに関しては非常に悩ましい&#12290 ;


ちなみに、自分の間抜けさを熟知しているプログラマほど自分で書くことを避ける。なにより、世の中で既に誰かが書いてて、公開されてて、かつ動いてるコードを僕がスクラッチから起こす必要はどこにもない。


 


とも思いつつ、だめもとで



NSExpression#expressionForFunction@”median:” argumentsSmilie: :(NSArray*)…



使ってみた。



—– [XXX:XXX] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[theObject compare:]: unrecognized selector sent to instance 0xXXXXXX’



いやいやいや…自分の間抜けさを実感させられただけでした。このログはtheObjectを直比較してるので、どの属性で比較したいか解らんと怒られてます。


それだけ?使えんじゃん!


 


ちなみにiOS8.4 SDKのヘッダにはこうコメントされている



+ (NSExpression *)expressionForFunctionSmilie: :(NSString *)name argumentsSmilie: :(NSArray *)parameters;
// Expression that invokes one of the predefined functions. Will throw immediately if the selector is bad; will throw at runtime if the parameters are incorrect.
// Predefined functions are:
// name parameter array contents returns
//————————————————————————————————————————————-
// sum: NSExpression instances representing numbers NSNumber
// count: NSExpression instances representing numbers NSNumber
// min: NSExpression instances representing numbers NSNumber
// max: NSExpression instances representing numbers NSNumber
// average: NSExpression instances representing numbers NSNumber
// median: NSExpression instances representing numbers NSNumber
// mode: NSExpression instances representing numbers NSArray (returned array will contain all occurrences of the mode)
// stddev: NSExpression instances representing numbers NSNumber
………. 



 つまり、公式に記述のあるOSX 10.5 laterにくわえ少なくともiOS8.4では最頻値まで使えるんじゃないか?


インチキ英語しか身につけてないので読み飛ばしただけかもしれないけど、だったらそう書け…


 


アルゴリズムとしては



  1. CoreDataから全アイテムをソートした配列にして取り出す

  2. 全配列から中央値出す -> 2Q

  3. lastObject -> 4Q

  4. 全配列前後を再帰させる -> 前1Q、後3Q


以上。ヒンジならこれで必要十分なはず、あとはこれをネタ元に配列が更新されたら四分位数再設定


sciPyのscoreatpercentileみたいにするなら四分位数からバイナリ探索して何percentileか出す。


レコード数が大きいから毎回Zone評価してリンク組み替えよりは算術探索の方が速い(はず…Smilie: ;) 。ステップ数は測ってないけど、NSSetのcontainsObject:は意外と遅いので…内部でループ回してるんじゃなかろうかと思うくらい。


テスト用に組み込んであるPythonの検算コードとも値は一致したので、とりあえず一件落着?


pyObjC組み込んだはいいけど、全然速度が出ないなんていう事態が怖くていまいち腰の重い僕でした。




from WordPress
How to Struggle Against S/W
http://ift.tt/1Ns1dKL


久々の投稿

長らく離れていたが、リンクをたどるとどうやら拙作のMarkdownエンジンAoBaneのDL数を稼いでいるもととなっているのはこのサイトらしい(仮)説が浮上してきたので、後日談と近況報告を兼ねて投稿。


AoBaneのDL数はおかげさまで今日現在24,767。割と稼いでる方だとは思う(全くお金にはなってないけど)。 ただ、たぶん以前の記事にもあると思うけど、このAoBaneの開発は止めてある。 理由はコストに見合わないから。AoBaneはRubyで書かれたBlueFeatherというエンジンがBaseになっていて、そこを部分的に間借りして<span>なんかを使って文字に色を付けたり、マーカーを入れたり、記号変換したり...してるわけだが、これが結構しんどい。


PHP MDExtraみたいな複雑な記述法を改変したり追加しようとしたら、仕様1つにつき基になっている記述を理解して、改修箇所を見極めてたら、改修とテストを含め一日仕事になる。そこで目を付けたのがScalaというFP言語。


試しにAoBaneの仕様を吸い上げてAkkaとか外部フレームワークも使わずフルスクラッチで書いたら1日で動き出した。しかもコード量も圧倒的に少なく、メンテナンスもしやすい(メンテナンス性は設計(表現法)に大きくよるので、表現力以外はScalaがすごいわけではないけど)。


 


ということで、SourceFourgeを主戦場に移して公開してるのがBlackQuillというScala製MarkDownエンジン。こっちの方が忠実にExtraの記法を実装してあり、追加機能も豊富、Markdownとしても表現力が高いので自分の書き物はもうこっちしか使ってない。


ただ、sbtとか環境を導入するのが面倒なためか、AoBaneよりはるかにパワフルなのにこっちは全くの閑古鳥。まぁ世の中そんなもん。AoBaneはgem installするだけだから...


 


一時期はMac AppstoreにGUIエディタ込みでBlackQuillを出そうともしてたけど、全然審査が通らない。2,3日でしびれを切らせて担当レビュアーに"じゃぁどうすりゃ通るのよ!"となかばプチ切れの英語で問い合わせたらjreをアプリにバンドルしないと通んないよだと。それもうJVMの利点を見事に殺してる...つまりMacアプリとしてJVMを使えば、アプリの数だけJVMがメモリ上に居座る。


まぁ、当時はAppleがJavaのシステムサポート切るよとオフィシャルで言った直後だったし、ちょっとした入れ違いもあったとはいえ、みるみるやる気をなくして未だitunesConnectにアップロード待ちのステータスで放置されている状態。


 


しかし、最近になってまずいことが起こった。(つまりここからがこの投稿の本題。)


FireFoxでも数式が見れない! 長いこと数式を使うような書き物がなかったため気づかなかったが、MathMLになにか変更が加わったのか、インポートしてる外部ライブラリに何かあったのか...。もう2年も前に描いたコードなので、そのレベルで見る気も起こらず、数式ジェネレータ部分は作り直すことに事にしました。


この投稿はその決意表明みたいなもの(ここまで読んで失望したした人ごめんなさい)です。ここ2日ほど下調べした感じ、2年前当時と違うのは幸か不幸か割とTeXを画像変換かつ多言語を埋め込めるようなWebサービスが出てきているところ。別サービスだけどついでにグラフ描画なんかもできるところもあったりする。


これならTeXを書いておいて実は内部的には<img>で画像埋め込みとかできるんじゃないか?というのが今回の改修の基本アプローチ。


本当はWolfram αが使えると1サイトで完結するからいいんだけど、叩いてみたらProにしろ(つまり金払え)ときたので今回はたぶん見送り。これに関しては利害関係が一致しないの一言につきる。あと、ユーザにMathematica構文を強要するのも雑だし、過去に書いたものを再変換するときにここの構文が変わってしまうといけてないので、コンバータが必要になるというジレンマ。ただ、数式だけならTeX構文をそのまま読み込むような機能もWolframにはあるのでやろうと思えばなんとかできそう。


 


何にせよ、MathMLはあまりに複雑でHTML5に変換されたときなに言ってるかさっぱりわかんなくなるので、シンプルな方に大きく倒すことにしたというお話。


あとはMarxicoみたいにMathJaxを使うアプローチにするか...少なくともMathMLを使っちゃったのが失敗だったというのは設計者として素直に認めるところではある。


ただ、できることならTeXも読んでて直感的になに書いたかわかりにくいから離れたいんだよなぁ。