🙆‍♀️

[Swift] aysncな関数内でThreadSafeでないインスタンスを扱う時は要注意

2023/12/12に公開

この記事は SwiftWednesday Advent Calendar 2023 の12日目の記事です。
https://qiita.com/advent-calendar/2023/swift-wednesday


aysnc・awaitで知らなかった挙動を発見したので共有します。
こんなコードを書いて実行してみたところ、実行時にスレッド違反でクラッシュしてしまいました。

class Todo: Object {
   @Persisted(primaryKey: true) var _id: ObjectId
   @Persisted var name: String = ""
}

func updateToDoName() async {
    let realm = try Realm()
    let todo = realm.objects(Todo.self)[0]
    let newName = await randamName()
    let currentName = todo.name // ここでクラッシュ
}

一見、 todo を生成したスレッドと currentName を取得したスレッドは変わっていないように見えます。
実際には await した時点でスレッドが変わっており、todo にアクセスするとクラッシュしていまいます。

Realmではどう対処するか

この問題に限らず、Threadを超えてRealmを扱うにはどうするのが良いのでしょうか?
ドキュメントには以下のような記述があります。

https://www.mongodb.com/docs/realm/sdk/swift/crud/threading/#summary

In order to see changes made on other threads in your realm instance, you must manually refresh realm instances that do not exist on "loop" threads or that have auto-refresh disabled.

For apps based on reactive, event-stream-based architectures, you can freeze objects, collections, and realms in order to pass shallow copies around efficiently to different threads for processing.

つまり、使用したいスレッドで再生成するか、 .freeze() を使うように勧めています。

RealmなどThreadSafeでない仕組みをConcurrencyと組み合わせて使う場合は、コードからだけでは気づき辛いこの問題に注意して頂ければと思います。

Discussion