RealmSwift vs CoreData

はじめに

iOSのローカルデータベースとして、これまでは、CoreDataが主流でした。
CoreDataの他だと直接SQLiteを弄ったり、ラッパーとなるFMDBなどのOSSがありましたが、Appleが提供している機能として、やはりCoreDataのシェア率は高かったと思います。

実際に、以下はAppleが公開している資料ですが、
CoreDataを採用する事で、純粋にSQLを叩くよりスピードが上がるや、メモリ効率も良い等、夢のようなツールとして紹介されています。

f:id:project-unknown:20180717010316p:plain:w430

しかし、CoreDataは学習コストが非常に高く、扱えるようになるまで時間が掛かったり、 扱えるようになったとしても、他のDBMSライクに扱おうとしたらどハマりしたりと、非常にクセが強いです。

iOSアプリの現場で、CoreDataを採用しているPJ等に配属された際に、困った人も多いのではないでしょうか。

そんな中登場したのが、RealmSwiftです。

f:id:project-unknown:20180717010114p:plain:w430

Realmは、公式サイトから紹介を引用すると

Realm Swiftはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。

実際に使ってみた感想だと、とにかく実装が楽です。
あまり考えなくてもデータの永続化もでき、UserDefaultsみたいなお手軽感があります。

しかし、

結論から先に言うと、TIME HACKERアプリを開発中に、私はCoreDataを選択し、RealmSwiftベースで作ってきたアプリの構造をCoreDataに移しました。

他のサイトでも、RealmSwiftとCoreDataの比較がなされているところが多いですが、私の主観でも書いてみようと思います。

リレーションシップをやろうとすると面倒

RealmSwiftでリレーションシップライクな事をやろうとすると、以下のように記述します。

FirstEntity

import UIKit
import RealmSwift

class FirstEntity: Object {
    @objc dynamic var id = NSUUID().uuidString
    @objc dynamic var second: SecondEntity? = nil                

    override static func primaryKey() -> String? {
        return "id"
    }
}

SecondEntity

import UIKit
import RealmSwift

class ActionEntity: Object {
    @objc dynamic var id = NSUUID().uuidString
    
    let firstEntity = LinkingObjects(fromType: FirstEntity.self, property: "second")  // FirstEntityがぶら下がる
}

上記のように書くと、SecondEntityにFirstEntityがぶら下がる形となり、
SecondEntityにFirstEntityを登録する事で、データを一気に登録・参照する事が可能になります。

これだけなら、非常に便利なのですが、 削除する時(Delete Rule)が問題になります。
RealmSwiftでは、DeleteRuleを設定することができないため、他のDBMSにあるように、親となるデータを削除した際に、子のデータも一緒に削除することはできません。
(私が開発していた時に、気づいていなかっただけなら申し訳ありません。)
ですので、RealmSwiftでデータ削除する際に、親も子も手動で削除していく必要があります。

反面、CoreDataの場合だと、

f:id:project-unknown:20180716180641p:plain:w430

こうする事で、親のデータを消したら、子のデータまで一気に消えます。


トランザクションが分かりにくい

これは完全に慣れの問題ですが、RealmSwiftの場合、コードブロック内でトランザクションが貼られます。

逆にいうとトランザクションから外れてコードを書くと思わぬところでデータ更新が走って詰まった事がありました。

CoreDataのコンテキスト内に更新ロジックを書いて、最後にsaveする書き方に慣れてしまったので、ここは凄く違和感を感じました。


マイグレーションに違和感がある

例えば以下の構成があったとします。

import UIKit
import RealmSwift

class FirstEntity: Object {
    @objc dynamic var id = NSUUID().uuidString
    @objc dynamic var updateDate? = Date()
}

これにデータを突っ込んで、
アップデートのタイミングで、以下のようにカラム名を修正します。

import UIKit
import RealmSwift

class FirstEntity: Object {
    @objc dynamic var id = NSUUID().uuidString
    @objc dynamic var createdDate? = Date()
}

この時、これまで入っていたDateが初期化されて、1970年が入ってました。

単純にマイグレーションする際の考慮漏れはありますが、CoreDataの場合だとここでクラッシュして気付けます。

RealmSwiftの場合だとデータを登録して結果を見て初めて気付くと言うところが個人的にはしんどいです。

実際に個人で開発しているときは、コアな箇所だとユニットテストを書いたり、ある程度のテストをおこないますが、それでも余暇の時間を使うのもあってテスト漏れは発生しやすいです。

この時に気付けるのか?というのがCoreDataを採用する1番の動機となりました。


締めくくり

結局のところで言うと、RealmSwiftに慣れていなくて、CoreDataは散々使ってきたので慣れていた。
が、CoreDataを採用した1番の理由です。

RealmSwiftは触ったから分かる素晴らしいものですが、私の学習が追いつかなかった。
RealmSwiftはサーバとのデータのやり取りも非常に楽に実装できるよう、サポートされていますし、いずれは使いこなせるにしていきたい限りです。

最後に

TIME HACKER

記事の途中で記載しましたが、今回の記事は、TIME HACKERを開発中の記事となります。

是非お使いください!