I recently added Swift compiler flags intended to catch concurrency warnings that will become errors in the future, adding -Xfrontend -warn-concurrency -enable-actor-data-race-checks to "Other Swift Flags" in my project build settings. These triggered both compile and runtime warnings about MainActor issues. The compile warns were about previews:
Static property '_previews' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol '_PreviewProvider', and
Static property '_platform' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol '_PreviewProvider'
But more troubling is the runtime console message:
warning: data race detected: @MainActor function at DataRace/DataRaceApp.swift:13 was not called on the main thread 2022-06-30 13:49:07.559517-0700 DataRace[2699:74516] warning: data race detected: @MainActor function at DataRace/DataRaceApp.swift:13 was not called on the main thread
The problem appears to be within the SwiftUI framework itself, and is easy to replicate:
-
Create a macOS SwiftUI document based app, I named my example "DataRace"
-
Add -Xfrontend -warn-concurrency -enable-actor-data-race-checks to "Other Swift Flags" in the project build settings
-
Change the Document declaration, changing it from a FileDocument to a ReferenceFileDocument, replacing fileWrapper() with the snapshot()/fileWrapper() required by ReferenceFileDocument:
class DataRaceDocument: ReferenceFileDocument {
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
required init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
public func snapshot(contentType: UTType) throws -> Data {
return text.data(using: .utf8)!
}
func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper {
return .init(regularFileWithContents: snapshot)
}
}
- Change the App DocumentGroup to the one appropriate for ReferenceFileDocument:
var body: some Scene {
DocumentGroup(newDocument: { DataRaceDocument() }) { file in
ContentView(document: file.document)
}
}
}
- Lastly, change the ContentView to accommodate the previous changes:
@ObservedObject var document: DataRaceDocument
var body: some View {
TextEditor(text: $document.text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(document: DataRaceDocument())
}
}
Compiling after these 4 changes will show the preview warnings. Run the app, save the default document, quit & restart the app and you will see the runtime warning.
The line that triggers it is in the DocumentGroup statement in the App:
DocumentGroup(newDocument: { DataRaceDocument() }) { file in
ContentView(document: file.document)
}
and breakpoints show it happening before DataRaceDocument.init() is called
Any ideas?