3
1

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 1 year has passed since last update.

Realm accessed from incorrect thread.

Last updated at Posted at 2022-01-10

#はじめに
去年からずっと悩んでいたRealmSwiftのエラーが最近、解決しました。

#原因
タイトルにもあるように不正なスレッドからレルムにアクセスしたことが原因です。

Realmのデータベース(以下、DB)では、異なるスレッド間でのアクセスは保証されていますが、スレッドを跨いでアクセスするのは禁止されています。

let realm = Realm() // メインスレッドで生成

DispatchQueue.global().async {
  // メインスレッドとは異なるスレッド

  realm.write {} // Realm accessed from incorrect thread.
}

#沼った部分
自分はずっとRealmインスタンスだけが、スレッド間を跨いでアクセスしなければOKだと思っていて、その思い込みのせいで沼にハマってしまいました。

解決の糸口は、Realm入門という書籍にありました。

#Realmの制約
Realmインスタンスは初期化した際にスレッドIDを内部で保持し、DBのアクセス時に初期化の時のスレッドIDと現在のスレッドIDを比較して異なるスレッドIDならエラーを吐く仕様になっています。

でこれはなにもRealmインスタンスに限った話ではありませんでした。

Realmではマネージドオブジェクト・アンマネージドオブジェクトがあって、Realmインスタンスやモデル定義(テーブル定義)する際に継承するObject、クエリが実行された時に返ってくるResults等がマネージドオブジェクトに該当します。

このマネージドオブジェクトでは必ず、内部でRealmインスタンスを持っており、値にアクセスする時はスレッドチェックが行われます。

そして、注意点としてObjectを継承したモデルオブジェクトは元々、アンマネージドオブジェクトでRealm.add()する事によってマネージドオブジェクトになります

// モデル定義
class HogeObject: Object {
  // 何らかの値
}

let realm = try! Realm() // メインスレッドで生成
let hogeObj = HogeObject() // この時点ではアンマネージドオブジェクト

try! realm.write {
// メインスレッドのRealmに保存している
realm.add(hogeObj) // add()することによってマネージドオブジェクトになる
}

let results = realm.objects(HogeObject.self) // メインスレッドのrealmから生成

DispatchQueue.global().async {
  // メインスレッドとは異なるスレッド

  // 各インスタンスはメインスレッドで生成しているので
  // ここでアクセスするとエラーが発生する

  print(hogeObj) // Realm accessed from incorrect thread.
  print(results) // Realm accessed from incorrect thread.
  realm.write { } // Realm accessed from incorrect thread.
} 

以上を踏まえて自分のコードを読んでみるとマネージドオブジェクトになったモデルオブジェクトを別スレッドで使っていたことが判明しました。

だからずっとエラーが消えなかったんですね。

因みにアンマネージドオブジェクトはスレッドを跨いでもOKです。

#解決方法①
Realmでは、異なるスレッド間でオブジェクトを渡せるようにThreadSafeReferenceが用意されていて本来であれば、これで解決します。

let realm = try! Realm() // メインスレッドで生成
let hogeObj = HogeObject()

try! realm.write {
  realm.add(hogeObj)
}

// マネージドオブジェクトのhogeObjを渡し、
// ThreadSafeReferenceインスタンスを生成する

let ref = ThreadSafeReference(to: hogeObj) // hogeObjへのスレッドセーフな参照を持つ

DispatchQueue.global().async {
  // メインスレッドとは異なるスレッド

  let realm = try! Realm() // サブスレッドで生成

  // 定数refはhogeObjへのスレッドセーフな参照を持つ
  // resolve()によって、サブスレッドのrealmインスタンスからhogeObjへの参照を解決(取り出す)ことが出来る
  guard let resolved = realm.resolve(ref) else {
    return
  }
  // マネージドオブジェクトにアクセスできるようになる
  print(resolved) // HogeObject
}

ただ今回は特殊ケースで、保存後・削除後のモデルオブジェクトを一時的に保持してそれを別スレッドで使います

保存後のモデルオブジェクトであれば、ThreadSafeReferenceでどうにかなりましたが、削除後のモデルオブジェクトはDBから削除されて無効なものになり、モデル定義したプロパティにアクセスするとエラーになります

なのでThreadSafeReferenceは参照できません。

let realm = try! Realm()
let hogeObj = HogeObject()

try! realm.write {
  realm.delete(hogeObj)
}
// 無効なオブジェクトで参照できないのでエラーになる
let ref = ThreadSafeReference(to: hogeObj) // Cannot construct reference to invalidated object

しかも、無効になった後もマネージドオブジェクトのままです。

#解決方法②
ではどうしたのかというと、会社の先輩を頼りました。

どうやって解決されたかというと、元々あるモデルオブジェクトとは別にObjectを継承していないモデルオブジェクトを定義してこの問題を回避していました。

// Objectを継承していないモデルオブジェクト
struct HogeObject {
  var hoge: String

  init() {
    let obj = HogeRealmObject()
    self.hoge = obj.hoge
  }

  // Realmオブジェクトに変換する
  func toRealmObject() -> HogeRealmObject {
    let obj = HogeRealmObject()
    obj.hoge = self.hoge
    return obj
  }

// Objectを継承しているモデルオブジェクト
  class HogeRealmObject: Object {
    @objc dynamic var hoge: String = ""
  }
}

let realm = try! Realm()
var hogeObj = HogeObject()
hogeObj.hoge = "hogehoge"

// Realmのオブジェクトに変換してDBに追加する
let realmObj = hogeObj.toRealmObject()

try! realm.write {
  realm.add(realmObj)
}

DispatchQueue.global().async {
  // マネージドオブジェクトではないので別スレッドでもアクセス可能
  print(hogeObj.hoge) // "hogehoge"
}

以上のやり方だと、保存後・削除後とか関係なく値にアクセスできるし、かつスレッド間を跨ぐことも出来ます。

#終わりに
とりあえず、年明け前の悩みが消えて良かったです。

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?