みずぴー日記

人間の再起動ボタンはハワイのビーチにある

☁️AquaSKKのiCloud対応

パッケージを作って ☁️AquaSKK with iCloud - みずぴー日記 に置いた。


AquaSKKのユーザ辞書をiCloudで同期するようにした。 自分の使っているMacBookiMacで辞書を共有できるようになった。

f:id:mzp:20151129095135p:plain

現状

cloudkitブランチに最新の内容がpushしてある。 いくつかのコーナーケースが残っているが、実用上の問題はないと思う。

もうちょっと完成度があがったら、ベータ版のインストーラとか作ると思う。

iCloudの使い方

iCloudを使う方法は4種類ある。 それぞれの特徴については[iOS 8] CloudKit を使ってみよう (1) 概要 | Developers.IOの冒頭に簡潔にまとまっている。 Appleの文書だとiCloud設計ガイド(PDF)がよい。

この4種類のどれを使うかはかなり悩んだが、今回はCloudKtiを使うことにした。

Key-value Storage
1MBまでしか保存できないので、容量が不足している。
iCloud document storage
ファイルベースを意識することになるので、単語登録したときの競合解決を実装するのが大変そう。
Core Data storage
現時点でCore Dataを用いていないので、あまり利点がなさそう。
CloudKit
iCloud上に構築されたKVSとして扱えるので、辞書データをそのまま格納できそう。 また、CloudKit dashboardで登録されたデータを確認できるので、開発が楽そう。

保存領域

CloudKitにはどのユーザでもアクセスできるpublic領域と、そのユーザしかアクセスできないprivate領域がある。 public領域の容量はアプリによって決まり、private領域の容量はユーザのアカウントによって決まる。(参考: Designing for CloudKit)

AquaSKKの辞書はprivate領域に格納するようにしたので、他のアカウントから辞書データにアクセスすることはできない。また辞書データはユーザのiCloud driveに保存される。

f:id:mzp:20151129103357p:plain

主な構造

f:id:mzp:20151129100702p:plain

SKKのエンジンが直接iCloudと通信するのを避け、中間にユーザ辞書を挟む方式を採用した。

  1. SKKのエンジンは、これまで通りユーザ辞書に対して単語の検索・登録を行なう。
  2. 同期するためのクラスがユーザ辞書を読み込み、更新分をiCloudに送信する。
  3. 定期的にiCloudの更新を確認し、更新があった場合はユーザ辞書に単語を追加する。

単語を検索するたびにiCloudと通信する方法も検討したが、ネットワーク通信ができない環境での利用を想定して断念した。

単語の削除

基本的に、iCloudから取得した単語をローカルの辞書にマージすることで、辞書の同期を実現している。 ただし、それだけでは、辞書から単語を削除しても簡単に復活してしまう。

そこで、「ユーザ操作によって削除された単語」は別枠で管理するようにし、ここに登録された単語は同期のタイミングでユーザ辞書から消すようにした。

同期状態の表示

同期状態を通知するために通知センターを利用している。

f:id:mzp:20151129095135p:plain

Dropboxのようにメニューバーに出しているアイコンで通知したかったが、InputMethodのアイコンを動的に変化させることができなったため断念した。

通知センターに用いられるアイコンはシステムにキャッシュされるらしく、AppIconを更新しても反映されなかった。 バージョンやビルド番号を更新すれば反映された。

設定画面

f:id:mzp:20151129102329p:plain

設定画面からiCloud同期の有効・無効化を切り替えれる。 無効にした場合はこれまでと同様の動作をする。

その他の知見

  • CloudKitの使い方はccabanero/ios-cloudkit-snippets · GitHubがシンプルにまとまってて、よかった。
  • テストにはL辞書にない単語を使う必要があったので、ゆるゆりの登場人物名を使っていた。 特に、同じ読みの単語がない「歳納」と、同じ読みの単語がある「赤座」の2つが便利だった。
  • AquaSKKのエンジン部はC++、CloudKitを利用する箇所はObjectvie-Cなので、Objective-C++を使うことになった。 いわゆる「文字列」に対応する型が、const char[]std::stringNSString*の3種類登場してつらかった。