【Swift】Realmのマルチスレッド対応!freezeとthawの違い

【Swift】Realmのマルチスレッド対応!freezeとthawの違い

この記事からわかること

  • Realm Swiftにおけるマルチスレッド対応
  • freezethawメソッド使い方
  • ThreadSafeReferenceとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

モバイル向けのデータベースを提供しているRealm Swiftの扱いの中でマルチスレッドにおける操作方法をまとめていきます。基本的な使い方や導入方法は以下の記事を参考にしてください。

マルチスレッド対応

Realmでは異なるスレッド間に置いてオブジェクトを操作することに制約(つまりスレッドセーフではない)が設けられており、適切に管理しないとスレッド違反でアプリがクラッシュする設計になっています。

スレッドセーフになっていないのはRealmデータベースを実際に操作するための「Realmインスタンス」と「管理下にあるオブジェクト(Managed Objects)」です。どうやらRealmインスタンスやオブジェクトはインスタンス化時に内部的に生成されたスレッドのIDを保持しており、異なるスレッドIDで参照された場合にスレッド違反を吐くようです。

// 生成したスレッドのIDを内部的に保持
let realm = Realm()

Managed ObjectsとUnmanaged Objects

まずRealmのデータオブジェクトは「Managed Objects」と「Unmanaged Objects」に分けられます。これは実際に「Realmデータベースに保存されているもの」と「されていないもの」の違いになります。

Realmをマルチスレッドで操作するための方法はいくつか存在します。

スレッドごとに操作

1つ目の方法はシンプルにスレッドごとに使用するRealmオブジェクトを切り替える方法です。Realmオブジェクトの有効範囲はインスタンス化したスレッドのみで別スレッドから操作しようとするとRealm accessed from incorrect thread.というエラーを吐きます。

Realmのインスタンス化は高速なのでスレッドごとに作成してもオーバーヘッドが少なく、パフォーマンス的にもそれほど影響はありません。

例えばスレッドA(メイン)からフェッチしたデータオブジェクトをバックグラウンドスレッドから参照したい場合はプライマリーキー(idプロパティなど)を使用して取得しなおします。この場合はプライマリーキーがスレッドセーフなプリミティブ型(StringやUUIDなど)である必要があります。

// メインスレッドで生成
let realm = try! Realm()
guard let object = realm.object(ofType: Shop.self, forPrimaryKey: "取得したいID") else { return }

// プライマリーキーをスレッドが変わる前に取得して変数に格納しておく
let id: String = object.id
DispatchQueue.global().async {
    // 別スレッドでは新しくRealmインスタンスを生成
    let realm = try! Realm()
    guard let sameObject = realm.object(ofType: Shop.self, forPrimaryKey: id) else { return }
    print("\(sameObject)")
}

freeze / thaw

2つ目の方法はfreezeメソッドを使用する方法です。freezeは言葉通り「凍結」の意味でデータオブジェクトを操作できない状態に変化させます。これによりデータオブジェクトに対してwriteなどによる変更などができなくなりますが、異なるスレッドに渡しても安全に値を参照することが可能になります。

let realm = try! Realm()
guard let frozenObject = realm.object(ofType: Shop.self, forPrimaryKey: "取得したいID")?.freeze() else { return }

DispatchQueue.global().async {
    print("\(frozenObject)")
}

凍結したオブジェクトを元に戻したい場合はthaw(解凍)メソッドを使用します。これによりwriteでの更新が可能になります。またthawメソッドは返り値がSelf?型になります。これはfreezeしている間に対象のオブジェクトがDBから削除されていた場合にnullになります。

let thawedObject = frozenObject.thaw()
try! realm.write {
    thawedObject?.name = "New Name"
}

またDBに未保存のデータ(Unmanaged Objects)からfreezeを呼び出すとUnmanaged objects cannot be frozen.というエラーを吐きアプリがクラッシュするので注意してください。

ThreadSafeReference

3つ目の方法はThreadSafeReferenceクラスを使用する方法です。ThreadSafeReferenceは引数に対象のオブジェクトを渡すことでインスタンス化することができ、別スレッドでresolveメソッドの引数に自身を渡すことで対象のオブジェクトを取得できるようになります。こちらはプライマリーキーがない場合などに活用できるかと思います。

let realm = try! Realm()
guard let object = realm.object(ofType: Shop.self, forPrimaryKey:  "取得したいID") else { return }
let reference = ThreadSafeReference(to: object)

DispatchQueue.global().async {
    let realm = try! Realm()
    if let resolvedObject = realm.resolve(reference) {
        print("\(resolvedObject)")
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

参考資料:Realm accessed from incorrect thread.

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index