[Swift] aysncな関数内でThreadSafeでないインスタンスを扱う時は要注意
この記事は SwiftWednesday Advent Calendar 2023 の12日目の記事です。
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を扱うにはどうするのが良いのでしょうか?
ドキュメントには以下のような記述があります。
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