iOSアプリのDBをCoredataからRealmへ
現時点の最新バージョン v0.96.2 での内容なので、バージョンによって違うかもしれないです。
GUIをインストール
ブラウザが無いと閲覧できない。最新バージョンのzipをDL。
公式の日本語ドキュメントは最新バージョン用ではない可能性があるのでgithubのURLから直接落とす方が安心。
https://github.com/realm/realm-browser-osx/releases/
Xcode Plugin
XcodeでModelファイルを新規作成するにはPluginを入れると簡単になるらしい。
Xcode Pluginは公式ドキュメントにならってAlcatrazからインストールする。
Xcode Pluginのインストールディレクトリパスはこちら
${HOME}/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins
Alcatrazのインストール
コマンドラインから実行
curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh
Xcodeを再起動する。
AlcatrazからRealmPluginをインストール
Alcatrazパッケージマネージャを起動
RealmPluginをインストール
開発の前に
開発する前に知っておかなければいけないこと。
Realmのテーブル定義方法
- RLMObjectサブクラスをコードで定義する(CoreDataのようなGUIは無く、ビューワのみ)
- カラムは
@property
で宣言する - @propertyの(nonatomic, strong)などは原則として無視される
- ただしメモリ上でのみ生きている場合はretainCountを意識する必要があるらしい(詳細は不明)
- リレーションシップを貼ることができる
- ただしSQLの
ON DELETE CASCADE
のような機能は現状無い
- ただしSQLの
- SQLの
LIMIT
に対応するものは無い
クラス名(テーブル)
63 bytes以下のUTF-8文字列
プロパティ名(カラム)
63 bytes以下のUTF-8文字列
対応しているデータ形
BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate truncated to the second, NSData, and NSNumber
NSData
データの保存は16MBまで
NSDate
マイクロ秒は保存されないのでNSTimeInterval推奨
プロパティのGetter/Setter
Overrideダメなので使うならKey-Value Observingで対応?
マルチスレッド非対応
RLMRealmオブジェクトはスレッドごとに取得する必要があるみたい。
[RLMRealm defaultRealm]
は問題ないのかもしれないけど、よくわからない。
開発する
CoreDataの開発をする場合は*.xcdatamodel(d)
を作成してModelクラスを作成する流れが多いかと思いますが、RealmはModelクラスだけを作成するのが一般的なようでした。
*.xcdatamodel(d)
に対応する*.realm
ファイルはどうやって作成+プロジェクトに組み込むといった説明が特に見当たらなかったので、実機から抜き出すのかなーとか思っています。
サンプル作る
とりあえずキーバリューなRealmサンプル作る
KVModel.h
/**
KeyValueデータモデル
*/
@interface KVModel : RLMObject
/** キー名 */
@property (nonatomic, strong) NSString * _Nonnull name;
/** バリュー値の保存用データ */
@property (nonatomic, strong) NSData * _Nonnull value;
// @property (nonatomic, strong) id _Nonnull value;
/**
valueのセッター。
データ形を判定して、セットする
*/
- (void)setObjectValue:(nonnull id)objectValue;
/**
valueのゲッター
適当なデータ形に変換して返す
*/
- (nullable id)objectValue;
@end
// This protocol enables typed collections. i.e.:
// RLMArray<KVModel>
RLM_ARRAY_TYPE(KVModel)
KVModel.m
@interface KVModel () {
id _objectValue;
}
@end
@implementation KVModel
+ (NSString *)primaryKey {
return @"name";
}
+ (NSDictionary *)defaultPropertyValues {
return @{};
}
// Specify properties to ignore (Realm won't persist these)
+ (NSArray *)ignoredProperties {
return @[];
}
- (void)setObjectValue:(nonnull id)objectValue {
self.value = [NSKeyedArchiver archivedDataWithRootObject:objectValue];
}
- (nullable id)objectValue {
if (!self.value) {
return nil;
}
id object = [NSKeyedUnarchiver unarchiveObjectWithData:self.value];
return object;
}
- (NSString *)description {
// return [NSString stringWithFormat:@"<%@ %p; name: %@, value: %@>",
// NSStringFromClass([self class]), self, self.name, self.value];
return [NSString stringWithFormat:@"<%@ %p; name: %@, objectValue: %@>",
NSStringFromClass([self class]), self, self.name, [self objectValue]];
}
AppDelegate.m
起動するだけで動作確認できるようにした
- (nonnull NSString *)realmPath {
// ドキュメントディレクトリにrealmファイルを作成する
NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [documentDirPath stringByAppendingPathComponent:@"dataStore.realm"];
NSLog(@"realm file path: %@", path);
return path;
}
- (nonnull NSData *)realmEncryptionKey {
// ランダムな暗号化キーを生成します
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
return key;
}
- (nullable RLMRealm *)realm {
// realm設定
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// realmファイルの暗号化設定
// config.encryptionKey = [self realmEncryptionKey]; // default null
// ファイルパスを設定
// config.path = [self realmPath];
NSError *error;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
// もし暗号化キーが間違っている場合、`error`オブジェクトは"invalid database"を示します
NSLog(@"Error opening realm: %@", error);
return nil;
}
return realm;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1つ目のモデル保存
KVModel *e1 = [[KVModel alloc] init];
NSDate *date = [NSDate date];
e1.name = [NSString stringWithFormat:@"%f", [date timeIntervalSince1970]];
[e1 setObjectValue:[NSString stringWithFormat:@"test value %f", [date timeIntervalSince1970]]];
RLMRealm *realm = [self realm];
[realm beginWriteTransaction];
[realm addObject:e1];
[realm commitWriteTransaction];
// 2つ目のモデル保存
KVModel *e2 = [[KVModel alloc] init];
date = [NSDate date];
e2.name = [NSString stringWithFormat:@"%f", [date timeIntervalSince1970]];
[e2 setObjectValue:@(1234567890)];
[realm beginWriteTransaction];
[realm addObject:e2];
[realm commitWriteTransaction];
// 読み込み
RLMResults *results = [KVModel allObjects];
for (KVModel *entity in results) {
NSLog(@"%@", entity);
}
return YES;
}
感想
とりあえず動いた。
KVModelのvalue
をid型にしたり、NSData型にしてNSKeyArchiverを使ったりしたのでソースは汚いです。
KVModel.valueをid型にして、NSStringで保存、NSNumberで保存、とした時、Realmファイルを覗いたら型名がAny
となっていました。なのでid型も一応保存可能みたいです。
ただid型はなんとなく先を考えるとリスクが高そうな気がするのでNSData+NSKeyedArchiver(UnArchiver)使った方が構造的にはシンプルかなと思いました。
あとは、今後マイグレーションする必要が出るかもしれないことを考えるとRealmファイル名をデフォルトから変更した方が幸せになれそうかなと思いました。