Swift 同時実行性 - Swift SDK
項目一覧
Swift の同時実行システムは、非同期コードと並列コードを構造化された方法で書込み (write) するための組み込みサポートを提供します。 Swift同時実行システムの詳細については、 Swiftプログラミング言語の同時実行トピックを参照してください。
このページの考慮事項は、Swift 同時実行機能で Realm を使用する際に広く適用されますが、Realm Swift SDK バージョン 10.39.0 では、Swift アクターで Realm を使用するためのサポートが追加されています。 単一のアクターに分離された Realm を使用することも、アクター全体で Realm を使用することもできます。
Realm のアクター サポートにより、mainActor およびバックグラウンド アクターのコンテキストで Realm の使用が簡素化され、同時実行性に関する考慮事項に関するこのページのアドバイスの多くが置き換えられます。 詳細については、 「 アクターで Realm を使用する - Swift SDK 」を参照してください。
Realm の同時実行に関する警告
アプリに同時実行機能を実装する際は、Realm のスレッド モデルと Swift の同時実行スレッド動作に関するこの警告を考慮してください。
Await による実行の一時停止
Swift キーワード await
を使用する場所はどこでも、コードの実行が停止する可能性のあるポイントをマークします。 Swift 5.7 では、コードが一時停止されると、後続のコードが同じスレッドで実行されない可能性があります。 つまり、コード内のawait
を使用しても、後続のコードは、その前後のコードとは異なるスレッドで実行される可能性があります。
これは本質的に Realm のライブ オブジェクト パラダイムと互換性がありません。 ライブ オブジェクト、コレクション、Realm インスタンスはスレッド定義です。つまり、これらは作成されたスレッドでのみ有効です。 具体的には、ライブ インスタンスを他のスレッドに渡すことはできません。 ただし、Realm にはスレッド間でオブジェクトを共有するためのいくつかのメカニズムが用意されています。 これらのメカニズムでは通常、コード間でデータを安全に渡すために明示的な処理を実行する必要があります。
固定オブジェクトやスレッドセーフリファレンスなどのこれらのメカニズムの一部を使用すると、 await
キーワードを持つスレッド全体で Realm オブジェクトとインスタンスを安全に使用できます。 また、非同期 Realm コードを@MainActor
でマークして、アプリが常にこのコードをメイン スレッドで実行するようにすることで、スレッド関連の問題を回避することもできます。
一般的なルールとして、スレッド保護を含めずにawait
コンテキストで Realm を使用すると、一貫性のない動作が発生する可能性があることに注意してください。 場合によっては、コードが成功する場合があります。 あるいは、誤ったスレッドでの書込みに関連するエラーがスローされる場合もあります。
Async/Await API
Atlas App Services アプリまたは同期された Realm の操作を伴う Realm Swift API の多くは、Swift の async/await 構文と互換性があります。 例については、以下をご覧ください。
同期された RealmまたはローカルRealm を開きます
Swift async/await API に関連する特定の機能リクエストがある場合は、 Realm 用の MongoDB フィードバック エンジン を確認してください。 Realm Swift SDK チームは、コミュニティ フィードバックと Swift の同時実行性の変化に基づいて、同時実行関連の機能を引き続き開発する予定です。
バックグラウンド書込み (write) の実行
非同期コードの一般的に要求されるユースケースは、メイン スレッドをブロックせず、バックグラウンドで書込み操作を実行することです。
Realm には、非同期書込みを実行できる 2 つの API があります。
writeAsync() API を使用すると、Swift 完了ハンドラーを使用して非同期書込みを実行できます。
asyncWrite() API を使用すると、Swift async/await 構文を使用して非同期書込みを実行できます。
これらの API ではどちらの API も使用でき、固定されたオブジェクトを使用したりスレッドセーフな参照を渡したりすることなく、バックグラウンドでオブジェクトを追加、更新、または削除できます。
writeAsync()
API では、書込みロック(write lock)の取得とトランザクションのコミットはバックグラウンドで発生します。 書込みブロック自体は呼び出しスレッドで実行されます。 これにより、固定されたオブジェクトを手動で処理したり、スレッド間で参照を渡したりする必要なしに、スレッドセーフ性が確保されます。
ただし、書込みブロック自体の実行中、呼び出し元のスレッド上の新しいトランザクションはブロックされます。 つまり、 writeAsync()
API を使用した大規模な書込みでは、実行中に小規模で高速な書込みがブロックされる可能性があります。
asyncWrite()
API は、スレッドをブロックするのではなく、書き込みを待つ間は、呼び出し元のタスクを一時停止します。 さらに、ディスクにデータを書き込む実際の I/O は、バックグラウンド ワーカー スレッドによって実行されます。 小規模な書込みの場合、メイン スレッドでこの関数を使用すると、バックグラウンド スレッドに書込みを手動でディスパッチするよりもメイン スレッドがブロックされる時間は短くなります。
コード例を含む詳細については、 「 バックグラウンド書き込みの実行 」を参照してください。
タスクと TaskGroup
Swift の同時実行性により、 タスク を管理するための API が提供されます および TaskGroups 。Swift 同時実行ドキュメント は、プログラムの一部として非同期に実行できる作業の単位としてタスクを定義します。タスクを使用すると、非同期作業の単位を具体的に定義できます。 TaskGroup を使用すると、親 TaskGroup の下のユニットとして実行するタスクのコレクションを定義できます。
Task と TaskGroup は、スレッドを他の重要な作業に渡したり、他の操作をブロックする可能性のある長時間実行のタスクをキャンセルしたりする機能を提供します。 これらの利点を得るには、 Tasks と TaskGroup を使用して Realm 書込み (write) をバックグラウンドで管理しなければならない場合があります。
ただし、上記のAwait による実行の一時停止で説明されているスレッド定義の制約は、Task コンテキストで適用されます。 タスクにawait
ポイントが含まれている場合、後続のコードが別のスレッドで実行または再開され、Realm のスレッド制限に違反する可能性があります。
Realm にアクセスするコードがメイン スレッドでのみ実行されるようにするには、Task コンテキストで実行する関数を@MainActor
で注釈を付ける必要があります。 これにより、Tasks を使用する利点の一部が否定され、ユーザー管理などのネットワーク アクティビティのみに Task を使用している場合を除き、Realm を使用するアプリの設計選択は適していない可能性があります。
アクターの分離
Tip
「 Swift アクターで Realm を使用する 」も参照してください
このセクションの情報は、Realm SDK バージョン 10.39.0 より前のバージョンに適用されます。 Realm Swift SDK バージョン 10.39.0 以降では、SDK は Swift アクターと関連する非同期機能とともに Realm の使用をサポートしています。
アクターの分離により、Realm アクセスは専用のアクターへのアクセスに制限されているという認識が提供されるため、非同期コンテキストで Realm アクセスを安全に管理できるようになります。
ただし、 @MainActor
以外の非同期関数での Realm の使用は現在サポートされていません。
Swift 5.6 では、これは多くの場合、一致によって機能します。 await
の後の実行は、待機しているものが実行されたスレッドで続行されます。 非同期関数でawait Realm()
を使用すると、アクター分離された関数を次に呼び出すまで、メインスレッドで実行されるコードは次のコードになります。
Swift 5.7 では、代わりにアクター分離コンテキストを変更するたびにスレッドがスローされます。 代わりに、分離されていない非同期関数は常にバックグラウンド スレッドで実行されます。
await Realm()
を使用し、5.6 で動作するコードがある場合、関数を@MainActor
としてマークすると、Swift 5.7 で動作するようになります。 5.6 では、意図せずにどのように実行されたかが関数されます。
同時実行コードに関連するエラー
同時実行コードによる Realm へのアクセスに関連するエラーは、ほとんどの場合、 Realm accessed from incorrect thread.
です。これは、このページで説明されているスレッド分離の問題が原因です。
Swift 同時実行機能を使用するコードでスレッド関連の問題を回避するには、次の手順に従います。
アクター分離された Realm をサポートする Realm Swift SDK のバージョンにアップグレードし、スレッドを手動で管理する代替として使用します。 詳細については、 「 アクターで Realm を使用する - Swift SDK 」を参照してください。
Realm にアクセスするときは、実行コンテキストを変更しないでください。 メイン スレッドで Realm を開き、UI のデータを提供する場合は、Realm に非同期にアクセスする後続の関数に
@MainActor
を使用して注釈を付け、常にメイン スレッドで実行されるようにします。await
は別のスレッドに変更可能な停止点をマークしています。アクター分離 Realm を使用しないアプリは、
writeAsync
API を使用して バックグラウンド書込み を実行できます。 これにより、自分で処理するための専用コードを記述しなくても、Realm アクセスをスレッドセーフに管理できます。 これは、書込みプロセスの一部をアウトソースする特別な API であり、非同期コンテキストで実行するために安全です。 アクター分離された Realm に書き込まれない限り、Swift のasync/await
構文ではこのメソッドは使用されません。 このメソッドは、コード内で同期的に使用します。 あるいは、非同期 Realm への書き込みを待機するときに、Swift のasync/await
構文でasyncWrite
API を使用することもできます。スレッド関連のクラッシュを回避するために、該当するスレッドに インスタンスを明示的に渡すことができます。 これには、Realm のスレッド モデルを十分に理解する必要と、Swift の同時実行スレッド動作に留意する必要があります。
送信可能タイプ、非送信タイプ、スレッド付きタイプ
Realm Swift SDK パブリック API には、次の 3 つの大きなカテゴリに分類されるタイプが含まれています。
送信可能
送信不可、およびスレッド定義なし
スレッドのみ
送信可能ではなく、スレッドが限定されない型をスレッド間で共有できますが、これらは同期する必要があります。
スレッド定義型は、固定されていない限り、分離コンテキストに制限されます。 同期を使用しても、これらのコンテキスト間でそれらを渡すことはできません。
送信可能 | Non-Sendable | スレッド構成 |
---|---|---|
任意の BSON | RMAppConfiguration | AnyRealmCollection |
AsyncOpen | RMFindOneAndModifyOptions | AnyRealmValue |
AsyncOpenSubscription | RMFindOptions | リスト |
RMAAPIKeyAuth | RMNetworkTransport | Map |
RMApp | RMRequest | MutableSet |
RLMAsyncOpenTask | RMResponse | プロジェクション |
RMchangeStream | RMSyncConfiguration | RRMArray |
RMCompensatedWriteInfo | RMSyncTimeoutOptions | RMchangeStream |
RLMCredentials | RRMDictionary | |
RMDecimal128 | RRMDictionary変更 | |
RLMEmailPasswordAuth | RM埋め込みオブジェクト | |
RMMaxKey | RMLinkingObjects | |
RLMMinKey | RMObject | |
RMMongoClient | RMPropertyCheck | |
RMMongoCollection | RLMRealm | |
RMMongoDatabase | RMResults | |
RMObjectId | RLMSection | |
RObjectSchema | RLMSectionedResults | |
RMProgressNotification | RLMSectionedResultschangeset | |
RMProgressNotificationToken | RMSet | |
RMProperty | RMSyncSubscription | |
RLMPropertyDescriptor | RMSyncSubscriptionSet | |
RPMProviderClient | Realm任意 | |
RLMpushClient | RealmProperty | |
RLMSschema | ||
RLMSortDescriptor | ||
RMSyncErrorActionToken | ||
RMSyncManager | ||
RRMSyncSession | ||
RMThreadセーフリファレンス | ||
RLMUpdateResult | ||
RLMuser | ||
RLMuserAPIKey | ||
RLMuserIdentity | ||
RLMuserProfile | ||
スレッドセーフ |