読者です 読者をやめる 読者になる 読者になる

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で全然走らなかったのと、関数の分け方に難があった、目的がjsoncsv化だったため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にコードを送り込んでいるんだけど、ん?と思うところがあったので、備忘。

 

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早くなんとかしてくれぃ。

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 
\overline{I_{n}} = \frac{\sum_{k=1}^{n} I_k}{n}
}
となる

じゃぁ、これに{ \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 
\overline{I_{n+1}} = \frac{\sum_{k=1}^{n+1} I_k}{ (n + 1)}
}
を算出するのに、すでに解っているのは、一つ前の平均値{ \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
X = \frac{1}{n + 1} (I_{n+1} - \overline{I_n}) }
ここで、
{ \displaystyle
\overline{I_{n+1}} = X + \overline{I_{n}}}だったので、

{ \displaystyle
\overline{I_{n+1}} = \overline{I_n} +  \frac{1}{n + 1} (I_{n+1} - \overline{I_n})
}
となる。つまり毎回、既にメモリ上にあるいっこ前までの平均値に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に対応遅れ感がどうしても残る。

 

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

 

*1:\(timeForm