Docs Menu
Docs Home
/ /
Atlas Device SDK
/

アクターで Realm を使用する - Swift SDK

項目一覧

  • 前提条件
  • このページの例について
  • アクター分離された Realm を開く
  • カスタム Realm アクターの定義
  • 分離された関数での Realm アクターの同期使用
  • 非同期関数での Realm アクターの使用
  • アクター分離された Realm への書き込み
  • アクターの境界全体で Realm データを渡す
  • スレッドセーフリファレンスを渡す
  • 送信可能なタイプを渡す
  • 別のアクターの通知を監視
  • 観察の制限
  • コレクション変更リスナーの登録
  • オブジェクト変更リスナーの登録

Realm Swift SDK バージョン 10.39.0 以降、Realm は、Swift アクターで Realm を使用するための組み込み機能をサポートします。 Realm のアクター サポートは、非同期作業を実行するためのスレッドまたはディスパッチ キューを管理する代替手段を提供します。 Realm をアクターとともに使用するには、次のような方法があります。

  • アクター分離された Realm を持つ特定のアクター上でのみRealm と連携します

  • アプリケーションのニーズに基づいて、アクター間で Realm を使用する

すべての Realm アクセスを 1 人のアクターに制限する場合は、アクター分離された Realm を使用することをお勧めします。 これにより、アクター境界を超えてデータを渡す必要がなくなり、データ競合のデバッグを簡素化できます。

複数のアクターで異なるタイプの作業を実行したい場合は、複数のアクター間で Realm を使用することをお勧めします。 たとえば、mainActor でオブジェクトを読み取りたいが、大規模な書き込みにはバックグラウンド アクターを使用したい場合があります。

Swiftアクターに関する一般的な情報については、 Apple のアクター ドキュメント を参照してください。

Swift アクターで Realm を使用するには、プロジェクトが次の条件を満たす必要があります。

  • Realm Swift SDK バージョン 10.39.0 以降を使用

  • Swift 5.8/Xcode 14.3 を使用

さらに、プロジェクトで次の設定を有効にすることを強くお勧めします。

  • SWIFT_STRICT_CONCURRENCY=complete: 厳密な同時実行チェックを有効にします

  • OTHER_SWIFT_FLAGS=-Xfrontend-enable-actor-data-race-checks: ランタイムアクター データ競合の検出を有効にします

このページの例では、次のモデルを使用します。

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

Swift async/await 構文を使用して、Realm が開かれるのを待機できます。

try await Realm()を使用して Realm を初期化すると、mainActor が分離された Realm が開きます。 あるいは、 await構文で Realm を開くときにアクターを明示的に指定することもできます。

@MainActor
func mainThreadFunction() async throws {
// These are identical: the async init produces a
// MainActor-isolated Realm if no actor is supplied
let realm1 = try await Realm()
let realm2 = try await Realm(actor: MainActor.shared)
try await useTheRealm(realm: realm1)
}

アクター分離された Realm を開くときに、デフォルト構成を指定することも、構成をカスタマイズすることもできます。

@MainActor
func mainThreadFunction() async throws {
let username = "Galadriel"
// Customize the default realm config
var config = Realm.Configuration.defaultConfiguration
config.fileURL!.deleteLastPathComponent()
config.fileURL!.appendPathComponent(username)
config.fileURL!.appendPathExtension("realm")
// Open an actor-isolated realm with a specific configuration
let realm = try await Realm(configuration: config, actor: MainActor.shared)
try await useTheRealm(realm: realm)
}

Realm の構成に関する一般的な情報については、「 Realm の構成とオープン - Swift SDK 」を参照してください。

同期された Realm をアクター分離された Realm として開くことができます。

@MainActor
func mainThreadFunction() async throws {
// Initialize the app client and authenticate a user
let app = App(id: APPID)
let user = try await app.login(credentials: Credentials.anonymous)
// Configure the synced realm
var flexSyncConfig = user.flexibleSyncConfiguration(initialSubscriptions: { subs in
subs.append(QuerySubscription<Todo>(name: "all_todos"))})
flexSyncConfig.objectTypes = [Todo.self]
// Open and use the synced realm
let realm = try await Realm(configuration: flexSyncConfig, actor: MainActor.shared, downloadBeforeOpen: .always)
try await useTheSyncedRealm(realm: realm)
}

同期された Realm を開くに関する一般的な情報については、「 同期された Realm を構成して開きます - Swift SDK 」を参照してください。

非同期コンテキストで Realm を管理するための特定のアクターを定義できます。 このアクターを使用して、Realm アクセスを管理し、書込み操作を実行できます。

actor RealmActor {
// An implicitly-unwrapped optional is used here to let us pass `self` to
// `Realm(actor:)` within `init`
var realm: Realm!
init() async throws {
realm = try await Realm(actor: self)
}
var count: Int {
realm.objects(Todo.self).count
}
func createTodo(name: String, owner: String, status: String) async throws {
try await realm.asyncWrite {
realm.create(Todo.self, value: [
"_id": ObjectId.generate(),
"name": name,
"owner": owner,
"status": status
])
}
}
func getTodoOwner(forTodoNamed name: String) -> String {
let todo = realm.objects(Todo.self).where {
$0.name == name
}.first!
return todo.owner
}
struct TodoStruct {
var id: ObjectId
var name, owner, status: String
}
func getTodoAsStruct(forTodoNamed name: String) -> TodoStruct {
let todo = realm.objects(Todo.self).where {
$0.name == name
}.first!
return TodoStruct(id: todo._id, name: todo.name, owner: todo.owner, status: todo.status)
}
func updateTodo(_id: ObjectId, name: String, owner: String, status: String) async throws {
try await realm.asyncWrite {
realm.create(Todo.self, value: [
"_id": _id,
"name": name,
"owner": owner,
"status": status
], update: .modified)
}
}
func deleteTodo(id: ObjectId) async throws {
try await realm.asyncWrite {
let todoToDelete = realm.object(ofType: Todo.self, forPrimaryKey: id)
realm.delete(todoToDelete!)
}
}
func close() {
realm = nil
}
}

アクター分離された Realm は、ローカル アクターまたはグローバル アクターのいずれかで使用できます。

// A simple example of a custom global actor
@globalActor actor BackgroundActor: GlobalActor {
static var shared = BackgroundActor()
}
@BackgroundActor
func backgroundThreadFunction() async throws {
// Explicitly specifying the actor is required for anything that is not MainActor
let realm = try await Realm(actor: BackgroundActor.shared)
try await realm.asyncWrite {
_ = realm.create(Todo.self, value: [
"name": "Pledge fealty and service to Gondor",
"owner": "Pippin",
"status": "In Progress"
])
}
// Thread-confined Realms would sometimes throw an exception here, as we
// may end up on a different thread after an `await`
let todoCount = realm.objects(Todo.self).count
print("The number of Realm objects is: \(todoCount)")
}
@MainActor
func mainThreadFunction() async throws {
try await backgroundThreadFunction()
}

関数が特定のアクターに制限されている場合、アクターを分離した Realm を同期的に使用できます。

func createObject(in actor: isolated RealmActor) async throws {
// Because this function is isolated to this actor, you can use
// realm synchronously in this context without async/await keywords
try actor.realm.write {
actor.realm.create(Todo.self, value: [
"name": "Keep it secret",
"owner": "Frodo",
"status": "In Progress"
])
}
let taskCount = actor.count
print("The actor currently has \(taskCount) tasks")
}
let actor = try await RealmActor()
try await createObject(in: actor)

関数が特定のアクターに限定されない場合は、Swift の async/await 構文で Realm アクターを使用できます。

func createObject() async throws {
// Because this function is not isolated to this actor,
// you must await operations completed on the actor
try await actor.createTodo(name: "Take the ring to Mount Doom", owner: "Frodo", status: "In Progress")
let taskCount = await actor.count
print("The actor currently has \(taskCount) tasks")
}
let actor = try await RealmActor()
try await createObject()

アクター分離された Realm では、非同期書き込みに Swift async/await 構文を使用できます。 try await realm.asyncWrite { ... }を使用すると、現在のタスクが一時停止され、現在のスレッドをブロックせずに書込みロックが取得され、そのブロックが呼び出されます。 Realm はバックグラウンド スレッドでデータをディスクに書込み、完了時にタスクを再開します。

上記で定義された例RealmActorのこの関数は、アクター分離された Realm に書き込む方法を示しています。

func createTodo(name: String, owner: String, status: String) async throws {
try await realm.asyncWrite {
realm.create(Todo.self, value: [
"_id": ObjectId.generate(),
"name": name,
"owner": owner,
"status": status
])
}
}

そして、Swift の非同期構文を使用してこの書き込みを実行できます。

func createObject() async throws {
// Because this function is not isolated to this actor,
// you must await operations completed on the actor
try await actor.createTodo(name: "Take the ring to Mount Doom", owner: "Frodo", status: "In Progress")
let taskCount = await actor.count
print("The actor currently has \(taskCount) tasks")
}
let actor = try await RealmActor()
try await createObject()

これは書き込みを待機している間は、呼び出しスレッドをブロックしません。 It does not perform I/O on the calling thread. 小規模な書込み(write)の場合、UI をブロックせずに@MainActor関数から安全に使用できます。 複雑さやプラットフォーム リソースの制約によりアプリのパフォーマンスに悪影響を与える書き込みは、バックグラウンド スレッドで実行するとメリットがある場合があります。

非同期書込みは、アクター分離された Realm または@MainActor関数でのみサポートされます。

Realm オブジェクトは 送信 できません 、 、および は、アクター境界を直接超えることはできません。アクター境界を超えて Realm データを渡すには、次の 2 つのオプションがあります。

  • アクターから または にThreadSafeReferenceを渡す

  • 値を直接渡したり、アクター境界をまたがる構造体を作成したりして、送信可能な他のタイプ渡します

オブジェクトへのアクセス権を持つアクターにスレッドセーフリファレンスを作成できます。 この場合では、 MainActorThreadSafeReferenceを作成します。 次に、宛先アクターにThreadSafeReferenceを渡します。

// We can pass a thread-safe reference to an object to update it on a different actor.
let todo = todoCollection.where {
$0.name == "Arrive safely in Bree"
}.first!
let threadSafeReferenceToTodo = ThreadSafeReference(to: todo)
try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo)

On the destination actor, you must resolve() the reference within a write transaction before you can use it. これにより、そのアクターにローカルなバージョンの オブジェクトが検索されます。

actor BackgroundActor {
public func deleteTodo(tsrToTodo tsr: ThreadSafeReference<Todo>) throws {
let realm = try! Realm()
try realm.write {
// Resolve the thread safe reference on the Actor where you want to use it.
// Then, do something with the object.
let todoOnActor = realm.resolve(tsr)
realm.delete(todoOnActor!)
}
}
}

重要

ThreadSafeReferenceは 1 回だけ解決する必要があります。 それ以外の場合、参照が解放されるまで、ソース邦土は固定されたままになります。 このため、 ThreadSafeReferenceの有効期間は短くする必要があります。

同じ Realm オブジェクトを複数回、アクター間で共有する必要がある場合は、プライマリキーを共有し、それを使用するアクターでクエリを実行することをお勧めします。 例については、このページの「プライマリ キーを渡し、別のアクター上のオブジェクトをクエリする」セクションを参照してください。

Realm オブジェクトは送信不可に 送信可能なタイプを渡し、アクターの境界をまたがるデータを操作するには、いくつかの戦略を使用できます。

  • 完全な Realm オブジェクトの代わりに、送信可能な Realm タイプまたはプリミティブ値を渡します

  • オブジェクトのプライマリキーを渡し、別のアクター上のオブジェクトに対するクエリを渡す

  • 構造体など、Realm オブジェクトの送信可能な表現を作成する

StringIntなど、Realm オブジェクトからの情報の一部だけが必要な場合は、Realm オブジェクトを渡す代わりにアクター間で値を直接渡すことができます。 どの Realm タイプが送信可能か完全なリストについては、「 送信可能タイプ、非送信タイプ、スレッド構成タイプ 」を参照してください。

@MainActor
func mainThreadFunction() async throws {
// Create an object in an actor-isolated realm.
// Pass primitive data to the actor instead of
// creating the object here and passing the object.
let actor = try await RealmActor()
try await actor.createTodo(name: "Prepare fireworks for birthday party", owner: "Gandalf", status: "In Progress")
// Later, get information off the actor-confined realm
let todoOwner = await actor.getTodoOwner(forTodoNamed: "Prepare fireworks for birthday party")
}

別のアクターで Realm オブジェクトを使用する場合は、使用するアクターでプライマリキーを共有し、それをクエリできます。

// Execute code on a specific actor - in this case, the @MainActor
@MainActor
func mainThreadFunction() async throws {
// Create an object off the main actor
func createObject(in actor: isolated BackgroundActor) async throws -> ObjectId {
let realm = try await Realm(actor: actor)
let newTodo = try await realm.asyncWrite {
return realm.create(Todo.self, value: [
"name": "Pledge fealty and service to Gondor",
"owner": "Pippin",
"status": "In Progress"
])
}
// Share the todo's primary key so we can easily query for it on another actor
return newTodo._id
}
// Initialize an actor where you want to perform background work
let actor = BackgroundActor()
let newTodoId = try await createObject(in: actor)
let realm = try await Realm()
let todoOnMainActor = realm.object(ofType: Todo.self, forPrimaryKey: newTodoId)
}

単純な値以上の値を操作する必要があるが、 ThreadSafeReferencesを渡したり、異なるアクター上のオブジェクトをクエリしたりするオーバーヘッドを避けたい場合は、アクター境界をまたがるデータの 構造体またはその他の送信可能な表現 を作成できます。 。

たとえば、アクターには、Realm オブジェクトの構造体表現を作成する関数があるとします。

struct TodoStruct {
var id: ObjectId
var name, owner, status: String
}
func getTodoAsStruct(forTodoNamed name: String) -> TodoStruct {
let todo = realm.objects(Todo.self).where {
$0.name == name
}.first!
return TodoStruct(id: todo._id, name: todo.name, owner: todo.owner, status: todo.status)
}

次に、関数を呼び出して、別のアクターの 構造体としてデータを取得できます。

@MainActor
func mainThreadFunction() async throws {
// Create an object in an actor-isolated realm.
let actor = try await RealmActor()
try await actor.createTodo(name: "Leave the ring on the mantle", owner: "Bilbo", status: "In Progress")
// Get information as a struct or other Sendable type.
let todoAsStruct = await actor.getTodoAsStruct(forTodoNamed: "Leave the ring on the mantle")
}

Swift の async/await 構文を使用して、アクター分離された Realm で通知を監視できます。

await object.observe(on: Actor)またはawait collection.observe(on: Actor)を呼び出すと、オブジェクトまたはコレクションが変更されるたびに呼び出されるブロックが登録されます。

SDK は、指定されたアクターの実行プログラム上のブロックを非同期に呼び出します。

異なるスレッドまたは異なるプロセスで実行された書込みトランザクションの場合、Realm が 変更を含むバージョンに (自動)更新されるときに、SDK は ブロックを呼び出します。 ローカル書込みの場合、SDK は書込みトランザクションがコミットされた後の将来のある時点でブロックを呼び出します。

の Realm 通知と同様に、Realm によって管理されるオブジェクトまたはコレクションのみを観察できます。 更新を監視するには、返されたトークンを保持する必要があります。

メインスレッドまたは別のアクターで監視対象の Realm の状態を手動で進める必要がある場合は、 await realm.asyncRefresh()を呼び出します。 これにより、Realm によって管理される Realm と未処理のオブジェクトが更新され、最新データを指し、該当する通知が送信されます。

.observe()メソッドは呼び出せません

  • 書込みトランザクション中

  • 含まれているRealmが読み取り専用の場合

  • アクターの外部から

SDK は、次のような書込みトランザクションごとにコレクション通知ブロックを呼び出します。

  • コレクションからオブジェクトを削除します。

  • コレクションにオブジェクトを挿入します。

  • コレクション内のオブジェクトの管理されているプロパティのいずれかを変更します。 これには、プロパティをその既存の値に設定する自己割り当てが含まれます。

重要

順序は重要

コレクション通知ハンドラーでは、削除、挿入、変更の順に常に変更を適用します。 削除前に挿入を処理すると、予期しない動作が発生する可能性があります。

これらの通知は、変更が発生したアクターに関する情報を提供します。 非アクター分離コレクション通知と同様に、書込みトランザクション中にどのオブジェクトが削除、追加、または変更されたかを報告するchangeパラメーターも提供します。 このRealmCollectionchangeは、 UITableViewのバッチ更新メソッドに渡すことができるインデックス パスの配列に解決されます。

// Create a simple actor
actor BackgroundActor {
public func deleteTodo(tsrToTodo tsr: ThreadSafeReference<Todo>) throws {
let realm = try! Realm()
try realm.write {
// Resolve the thread safe reference on the Actor where you want to use it.
// Then, do something with the object.
let todoOnActor = realm.resolve(tsr)
realm.delete(todoOnActor!)
}
}
}
// Execute some code on a different actor - in this case, the MainActor
@MainActor
func mainThreadFunction() async throws {
let backgroundActor = BackgroundActor()
let realm = try! await Realm()
// Create a todo item so there is something to observe
try await realm.asyncWrite {
realm.create(Todo.self, value: [
"_id": ObjectId.generate(),
"name": "Arrive safely in Bree",
"owner": "Merry",
"status": "In Progress"
])
}
// Get the collection of todos on the current actor
let todoCollection = realm.objects(Todo.self)
// Register a notification token, providing the actor where you want to observe changes.
// This is only required if you want to observe on a different actor.
let token = await todoCollection.observe(on: backgroundActor, { actor, changes in
print("A change occurred on actor: \(actor)")
switch changes {
case .initial:
print("The initial value of the changed object was: \(changes)")
case .update(_, let deletions, let insertions, let modifications):
if !deletions.isEmpty {
print("An object was deleted: \(changes)")
} else if !insertions.isEmpty {
print("An object was inserted: \(changes)")
} else if !modifications.isEmpty {
print("An object was modified: \(changes)")
}
case .error(let error):
print("An error occurred: \(error.localizedDescription)")
}
})
// Update an object to trigger the notification.
// This example triggers a notification that the object is deleted.
// We can pass a thread-safe reference to an object to update it on a different actor.
let todo = todoCollection.where {
$0.name == "Arrive safely in Bree"
}.first!
let threadSafeReferenceToTodo = ThreadSafeReference(to: todo)
try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo)
// Invalidate the token when done observing
token.invalidate()
}

SDK は、次のような書込みトランザクションごとにオブジェクト通知ブロックを呼び出します。

  • オブジェクトを削除します。

  • オブジェクトの管理されているプロパティのいずれかを変更します。 これには、プロパティをその既存の値に設定する自己割り当てが含まれます。

ブロックには、リクエストされたアクターに分離されたオブジェクトのコピーと、変更された内容に関する情報が渡されます。 このオブジェクトは、そのアクターで安全に使用できます。

デフォルトでは、オブジェクトのプロパティに対する直接変更のみが通知を生成します。 リンクされたオブジェクトに対する変更では通知は生成されません。 nil 以外の、空でないキーパス配列が渡される場合、それらのキーパスで識別されるプロパティに対する変更のみが変更通知を生成します。 キーパスは、リンクプロパティを走査して、リンクされたオブジェクトの変更に関する情報を受け取る場合があります。

// Execute some code on a specific actor - in this case, the MainActor
@MainActor
func mainThreadFunction() async throws {
// Initialize an instance of another actor
// where you want to do background work
let backgroundActor = BackgroundActor()
// Create a todo item so there is something to observe
let realm = try! await Realm()
let scourTheShire = try await realm.asyncWrite {
return realm.create(Todo.self, value: [
"_id": ObjectId.generate(),
"name": "Scour the Shire",
"owner": "Merry",
"status": "In Progress"
])
}
// Register a notification token, providing the actor
let token = await scourTheShire.observe(on: backgroundActor, { actor, change in
print("A change occurred on actor: \(actor)")
switch change {
case .change(let object, let properties):
for property in properties {
print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'")
}
case .error(let error):
print("An error occurred: \(error)")
case .deleted:
print("The object was deleted.")
}
})
// Update the object to trigger the notification.
// This triggers a notification that the object's `status` property has been changed.
try await realm.asyncWrite {
scourTheShire.status = "Complete"
}
// Invalidate the token when done observing
token.invalidate()
}

戻る

SwiftUI プレビューで Realm を使用する