Is there no other solution than moving everything from the nice property wrapper annotation to init? AppSettings runs on the MainActor to avoid data races when accessing settings, I could make it non-isolated but then other means to prevent data races are required.
After reading the swift-evolution proposal 411 I still do not understand the error. The implicit initializer of MyApp is isolated to the MainActor hence the implicit initialization of rebuildDatabase should also be done on the MainActor, right? All participating code fragments are isolated to MainActor, still there is an error from the compiler.
I cannot elaborate more on the topic, unfortunately. I might as well misunderstand that part from SE-411 and current behaviour is actually should be addressed by the compiler. My understanding initially was the same as yours: everything main actor isolated, that should work – just yesterday it was the time for me to address this issue in my codebase with @dynamicMemberLookup, and moving to init was the only way for me to fix it, and this reasoning from SE-411 came after.
I worked around the compiler error by only MainActor-isolating the shared property of AppSettings and passing all accesses to AppSettings through the AppSetting property wrapper, which is isolated to MainActor. This is race-free, but I firmly believe the compiler should accept the initial sources since they are also race-free (until proven otherwise of course).
final class AppSettings {
var rebuildDatabase: Bool = false
@MainActor static let shared = AppSettings()
}
@MainActor
struct MyApp {
@AppSetting(\.rebuildDatabase) private var rebuildDatabase
}
actor DB {
func f() async {
let v = await MainActor.run {
AppSettings.shared.rebuildDatabase
}
}
}
@MainActor
@propertyWrapper
struct AppSetting<Value> {
private let keyPath: ReferenceWritableKeyPath<AppSettings, Value>
private let preferences: AppSettings
init(_ keyPath: ReferenceWritableKeyPath<AppSettings, Value>) {
self.keyPath = keyPath
self.preferences = .shared
}
var wrappedValue: Value {
get { preferences[keyPath: keyPath] }
nonmutating set { preferences[keyPath: keyPath] = newValue }
}
static subscript<T>(
_enclosingInstance instance: T,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
) -> Value {
get {
instance[keyPath: storageKeyPath].wrappedValue
}
set {
instance[keyPath: storageKeyPath].wrappedValue = newValue
}
}
}