[React Native] データ永続化についてAsyncStorageとRealmを調べてみた

※記事執筆時点のReact Nativeのバージョンは0.43です

# はじめに
iOSやAndroidのアプリでは、サーバーAPIから新しいデータを取得する前に、キャッシュしておいたデータをユーザーに対して表示するということはよくあります。アプリが終了してもキャッシュを保持しておきたい場合、対象のデータを端末に保存する（永続化する）必要があります。
今回は、React Nativeでの開発において、データを永続化するためにどういう手段があるのか調べてみました。

# React Nativeとデータ永続化

React Nativeでデータを永続化する方法は色々あります。React Native標準の仕組みには[AsyncStorage](https://facebook.github.io/react-native/docs/asyncstorage.html)というものがあり、iOSとAndroidに両方対応しています。

AsyncStorageの他には、Realm、SQLite、Couchbase、MongoDBといった選択肢があります。

[awesome-react-native](https://github.com/jondot/awesome-react-native) で今日時点でスターが多いライブラリは、[realm](https://github.com/realm/realm-js)や [react-native-storage](https://github.com/sunnylqm/react-native-storage) （AsyncStorageのラッパー）や [react-native-sqlite-storage](https://github.com/sunnylqm/react-native-storage) （SQLiteのラッパー）となっています。


- react-native-couchbase-lite ★71 - couchbase lite binding for react-native
- react-native-db-models ★145 - Local DB Models for React Native Apps
- react-native-level-fs ★12 - fs for react-native using level-filesystem and asyncstorage-down
- react-native-mongoose ★10 - A AsyncStorage based mongoose like storage for react-native
- react-native-pouchdb ★30 - Run pouchdb in React Native!
- react-native-simple-store ★344 - A minimalistic wrapper around React Native's AsyncStorage.
- react-native-sqlite-storage ★668 - SQLite3 bindings for React Native (Android & iOS)
- react-native-sqlite ★474 - SQLite3 bindings for React Native
- react-native-sqlite-2 ★13 - SQLite3 Native Plugin for React Native for both Android and iOS
- react-native-storage ★715 - This is a local storage wrapper for both react-native(AsyncStorage) and browser(localStorage). ES6/babel is needed.
- react-native-store ★442 - A simple database base on react-native AsyncStorage.
- realm ★1755 - An alternative mobile database to SQLite & key-value stores.
- pouchdb-adapter-react-native-sqlite ★11 - PouchDB adapter using ReactNative SQLite as its backing store
- react-native-persistent-job ★55 - Run async tasks that retry after a crash, connection loss or exception


# AsyncStorage
AsyncStorageは、シンプルな、暗号化されていない、非同期の、永続的な、key-valueストアです。   
Androidアプリの場合は、RocksDBかSQLiteのどちらか利用可能な方にデータが保存されます。  
iOSアプリの場合は、ネイティブのコードが実行され、シリアライズされたdictionaryとしてデータがファイルに保存されます。[React Nativeのコード](https://github.com/facebook/react-native/blob/master/React/Modules/RCTAsyncLocalStorage.m)をざっと読んだところ、ネイティブの `writeToFile` メソッドでファイルを書き込むという実装になっていました。

## コード

公式ドキュメントのコードです。各メソッドではPromiseオブジェクトが返却されます。

```js
try {
  await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.');
} catch (error) {
  // Error saving data
}
```

```js
try {
  const value = await AsyncStorage.getItem('@MySuperStore:key');
  if (value !== null){
    // We have data!!
    console.log(value);
  }
} catch (error) {
  // Error retrieving data
}
```

`margeItem()`や`multiMerge()`のメソッドを使うと、既に存在するキー・値と、新しい入力の値を統合することができます。

```js
let UID123_object = {
 name: 'Chris',
 age: 30,
 traits: {hair: 'brown', eyes: 'brown'},
};
// You only need to define what will be added or updated
let UID123_delta = {
 age: 31,
 traits: {eyes: 'blue', shoe_size: 10}
};

AsyncStorage.setItem('UID123', JSON.stringify(UID123_object), () => {
  AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta), () => {
    AsyncStorage.getItem('UID123', (err, result) => {
      console.log(result);
    });
  });
});

// Console log result:
// => {'name':'Chris','age':31,'traits':
//    {'shoe_size':10,'hair':'brown','eyes':'blue'}}
```


# Realm
iOSやAndroidのネイティブ開発で採用される例が増えている、モバイルアプリケーション向けデータベースです。  
[Realm: リアクティブなモバイルアプリを短期間に](https://realm.io/jp/docs/javascript/latest)の記事では、以下がRealmを使うべき理由であると書かれています。

- 使い方が簡単
- 速い！
- クロスプラットフォーム
- 先進的
- 信頼性
- コミュニティドリブン
- サポート

Realmはデータベースであり、（特にiOSの場合、データをファイルに書き込む）AsyncStorageとは仕組みが異なります。AsyncStorageやそのラッパーを使った場合とは、クエリの使い勝手や速度面で差が出そうです。

なお上記記事の[ベンチマーク結果](https://realm.io/jp/blog/introducing-realm-react-native/#benchmarks)では、20万件のレコード（1000件のマッチ）のクエリにおいて、SQLiteよりも速く、AsyncStorageの185倍のパフォーマンスが出ています。




### コード

[Realm JavaScript 2.0.12](https://realm.io/docs/javascript/2.0.12) のコードです。  
スキーマを定義する必要があります。  
ネイティブ開発でRealmを使ったことがある人は、戸惑うことなくコードを書いていけそうです。

```js
const Realm = require('realm');

// Define your models and their properties
const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};
const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    birthday: 'date',
    cars:     'Car[]',
    picture:  'data?' // optional property
  }
};

Realm.open({schema: [CarSchema, PersonSchema]})
  .then(realm => {
    // Create Realm objects and write to local storage
    realm.write(() => {
      const myCar = realm.create('Car', {
        make: 'Honda',
        model: 'Civic',
        miles: 1000,
      });
      myCar.miles += 20; // Update a property value
    });

    // Query Realm for all cars with a high mileage
    const cars = realm.objects('Car').filtered('miles > 1000');

    // Will return a Results object with our 1 car
    cars.length // => 1

    // Add another car
    realm.write(() => {
      const myCar = realm.create('Car', {
        make: 'Ford',
        model: 'Focus',
        miles: 2000,
      });
    });

    // Query results are updated in realtime
    cars.length // => 2
  })
  .catch(error => {
    console.log(error);
  });
```

# 所感
シンプルな設定値を扱う場合や、検索条件を指定して取得する必要が無いデータを扱う場合は、AsyncStorageだと機能を手軽に実装できます。iOSネイティブ開発でNSUserDefaultsに保存していたようなデータもAsyncStorageで扱えばよいでしょう。  
処理速度が重要になる場合や、iOSネイティブ開発においてCoreDataで管理するようなデータを扱う場合は、予めRealmや他のDBを検討しておくと良さそうです。


# さいごに
ベンチマークについても書きたかったのですが、今日はここまで。運用についての知見も得られれば、別途書きたいと思います。

# 参考
- [AsyncStorage · React Native](https://facebook.github.io/react-native/docs/asyncstorage.html)
- [Realm: リアクティブなモバイルアプリを短期間に](https://realm.io/jp/docs/javascript/latest)
- [Realm React Nativeを公開](https://realm.io/jp/blog/introducing-realm-react-native/)
- [jondot/awesome-react-native: Awesome React Native components, news, tools, and learning material!](https://github.com/jondot/awesome-react-native)
- [What are my options for storing data when using React Native? (iOS and Android) - Stack Overflow](https://stackoverflow.com/questions/44376002/what-are-my-options-for-storing-data-when-using-react-native-ios-and-android)
- [React Nativeにおけるローカルデータベースの考察 | Waza Lab](http://www.wazalab.com/2016/08/14/react-native%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E8%80%83%E5%AF%9F/) 
- [React NativeでTwitterクライアントを作ってみよう](https://www.slideshare.net/dcubeio/20161208-react-nativetwitter-70273273)
- [React NativeでRealmを使ったアプリ設定の簡易保存 - Qiita](https://qiita.com/ShingoFukuyama/items/0c87b1c4b804e6219cc8)