111
78

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React NativeAdvent Calendar 2017

Day 3

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

Last updated at Posted at 2017-12-03

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

はじめに

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

React Nativeとデータ永続化

React Nativeでデータを永続化する方法は色々あります。React Native標準の仕組みにはAsyncStorageというものがあり、iOSとAndroidに両方対応しています。

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

awesome-react-native で今日時点でスターが多いライブラリは、realmreact-native-storage (AsyncStorageのラッパー)や react-native-sqlite-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のコードをざっと読んだところ、ネイティブの writeToFile メソッドでファイルを書き込むという実装になっていました。

コード

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

try {
  await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.');
} catch (error) {
  // Error saving data
}
try {
  const value = await AsyncStorage.getItem('@MySuperStore:key');
  if (value !== null){
    // We have data!!
    console.log(value);
  }
} catch (error) {
  // Error retrieving data
}

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

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: リアクティブなモバイルアプリを短期間にの記事では、以下がRealmを使うべき理由であると書かれています。

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

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

なお上記記事のベンチマーク結果では、20万件のレコード(1000件のマッチ)のクエリにおいて、SQLiteよりも速く、AsyncStorageの185倍のパフォーマンスが出ています。

コード

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

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を検討しておくと良さそうです。

さいごに

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

参考

111
78
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
111
78

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?