iOSアプリでParse Local Datastoreを使ってみた


# 背景

最近、個人的なiOSアプリ開発でParseを使っている。Parseでは、Local Datastore(ローカルデータストア)という便利そうな機能が以前から発表されており、気になっていた。
そして先月やっと、iOS向けのParseSDKに組み込まれたようだ。試しに使ってみたので、簡単な使い方と使ってみた結果を共有する。

# <font color="red">注意事項</font>

Parse Local Datastoreは簡単に使えてとても便利な機能なのだが
**挙動に致命的な問題があったため、現時点での利用は見送った。**
問題点の内容は後述しているので、参考にしてほしい。
改善されたSDKバージョンのリリースが確認されたときは、この記事にも反映する予定。

確認バージョン: ParseSDK 1.6.1 (iOS向け)

# Parse Local Datastore とは

まず、[Parseについての説明はこちらを参照。](http://qiita.com/kyproject/items/b9891aa7bb596b958d7e)

Local Datastoreは、Parseがクラウドに保持しているDBの内容を、アプリローカルのデータベースにも保存して使える機能。
iOSアプリだと、sqliteのデータとして保存される。

ローカルにクラウドのデータを保存できるとどんな嬉しい事があるかは、次のページで詳しく解説されている。

[クラウド同期アプリが手軽に作れるかも Parse Local Datastore for iOSがやっと出た | うめのんブログ](http://umenon.com/2014/12/10/parselocaldatastoreforios/)




# 基本的な使い方

ローカルデータストアを利用するのに難しい設定は必要ない。
既存のコードに2, 3行追加するだけで恩恵を受けられるようになる。

## 1.ローカルデータストアの有効化

AppDelegate.mの`didFinishLaunchingWithOptions:`内で設定する。
`setApplicationId:`の **「前に」** 有効化が必要。
有効化をしないと、ローカルデータストアにアクセスしようとした時にエラーになる。

```objc:AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [Parse enableLocalDatastore]; // 1.ローカルデータストアを有効に
    [Parse setApplicationId:PARSE_APP_ID clientKey:PARSE_CLIENT_KEY];
    // (略)
    return YES;
}
```

## 2.ローカルに保存する

PFObjectに対して、pinで始まるメソッドを実行すればそれだけでローカルに保存してくれる。

```objc:save_object_to_local.m
[object pinInBackground]; // ローカルに保存
```

NSArray中のPFObjectもまとめて保存することができる。
たとえば取得してきたオブジェクトのリストに対して、`pinAllObjectsInBackground:`が使えた。

```objc:save_array_to_local.m
// PFQueryでクラウドからオブジェクトを取得
PFQuery *query = [PFUser query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    [PFObject pinAllObjectsInBackground:objects]; // ローカルに保存
}];
```



## 3.ローカルから検索する

PFQueryに対して`fromLocalDatastore`を指定してやると、ローカルデータストアから検索できる。
それ以外については、普段のPFQueryの使い方と変わらない。
 
```objc:fetch_from_local.m
PFQuery *query = [PFUser query];
[query fromLocalDatastore]; // ローカルデータストアからの検索を指定
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    // ローカルからの検索結果を利用
}];
```

# 問題点

ローカルデータストアの使い方はとても簡単だった。
かなり便利に使えそうである。
使えそうなのだが、現時点だと大きな問題点があったので記述しておく。

## ローカルへの保存が、メインスレッドで処理される

なんてこった／(^o^)＼
pinAllInBackgroundのようにInBackgroundを指定するメソッドであっても、バックグラウンド処理してくれてない。
完全にUIスレッドを止めてしまっている。
ローカルデータストアに書き込みをしている最中は、アプリが操作不能になってしまった。

仕方がないので、書き込み処理は[別スレッドで処理](http://qiita.com/kyproject/items/2432b01ebfe449272812)しよう。

## 処理が遅すぎる

そしてこれが致命的だった。
ローカルデータストア周りの処理が致命的に遅い。
保存も、読み込みも両方だ。

試しに、読み込みにかかる時間を測ってみた。

### 処理速度の測定

以下のコードで、データの読み込みにどの程度時間がかかるかを測った。

実行環境: iPod touch 第5世代

```objc:
PFQuery *query = [Comment query];
[query fromLocalDatastore]; // ローカルに保存した分のみ
NSLog(@"start fetch");
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    NSLog(@"finish fetch %d records", objects.count);
}];
```

### ローカルの処理なのに2秒もかかる！

```
2015-01-04 21:15:50.448 Sample[1523:60b] start fetch
2015-01-04 21:15:52.658 Sample[1523:60b] finish fetch 100 records
```

え？
ネットワーク越しの処理じゃないのに？
ローカルのDBから読み込んでるだけなのにそんなにかかるの？

しかも...

### キャッシュが効いていないと(？)0レコードでも5秒以上かかる

実行タイミングに依っては、5秒以上かかることもあった。
原因は分からないが、DBでのキャッシュの有無の違いからだろうか？
同じクエリを複数回投げると、2回目からは2秒程度になった。

```
2015-01-04 21:15:19.890 Sample[1523:60b] start fetch
2015-01-04 21:15:24.935 Sample[1523:60b] finish fetch 0 records
```

なにかこの速度を改善できるような方法がないか探したが
**今のところ対策が見つかっていない。**

次のリリースで改善されれば良いのだが…。

# まとめ

* ローカルデータストアのAPI自体はめちゃめちゃ使いやすい。数行加えるだけで利用可能。

ただ、

* 実行速度が致命的に遅い。読み込みに2秒以上かかる。

よって、

現時点ではこの問題点が改善されるまでは使えない...という判断。


# 参考

Parse Local Datastore for iOS | Parse Blog
http://blog.parse.com/2014/12/09/parse-local-datastore-for-ios/

Parse iOS SDK Reference
http://parse.com/docs/ios/api/ 

クラウド同期アプリが手軽に作れるかも Parse Local Datastore for iOSがやっと出た | うめのんブログ
http://umenon.com/2014/12/10/parselocaldatastoreforios/