Run CloudKit record mapping on the main actor to fix SwiftData race

CloudKitRecordMapper was a standalone actor, so 'await recordMapper.toCKRecord'
from the @MainActor sync engine hopped off the main actor and read live
SwiftData @Model properties (e.g. LocalPlaylistItem.authorName) from the wrong
executor. SwiftData models are bound to the main ModelContext and are not
thread-safe, so this raced the backing store and crashed with EXC_BAD_ACCESS.

Make the mapper @MainActor (it must touch main-isolated models regardless) and
drop the now-redundant awaits. Fixes the crash for all synced model types.

Fixes an EXC_BAD_ACCESS seen in TestFlight build 261.
This commit is contained in:
Arkadiusz Fal
2026-05-13 20:56:03 +02:00
parent 926c6ebc97
commit ba33823048
2 changed files with 56 additions and 48 deletions

View File

@@ -9,7 +9,15 @@ import CloudKit
import Foundation
/// Maps SwiftData models to CloudKit records and vice versa.
actor CloudKitRecordMapper {
///
/// This MUST be `@MainActor`: the `toCKRecord(...)` methods read live SwiftData
/// `@Model` properties (e.g. `LocalPlaylistItem.authorName`) and the `to<Model>`
/// methods create/insert models. SwiftData models are bound to the main
/// `ModelContext` and are not thread-safe when this was a standalone `actor`,
/// `await recordMapper.toCKRecord(...)` hopped off the main actor and read the
/// backing store from the wrong executor, causing EXC_BAD_ACCESS crashes.
@MainActor
final class CloudKitRecordMapper {
private let zone: CKRecordZone
/// Current schema version for CloudKit records.