I'm adapting my state management library to Swift 6 language mode, and the issue I can't overcome is my use of unapplied method references for a pure swift (no objc runtime) target action pattern which avoids circular retains for you. A simplified example is below.
It's important that the target action pattern is synchronous. I'm aware that making my action handler async will compile and eventually trigger my action handler in a delayed way, but this isn't my desired behavior. For instance, without synchronous updates I can't use this pattern to configure a ViewController fully within viewDidLoad, leading to a flash of unloaded state.
It's also necessary for my state management to run within MainActor. Making the sample code classes that implement the target action pattern nonisolated compiles and works correctly. But my actual implementation depends on being MainActor.
What I don't understand is if a) The inability to use unapplied methods with structured concurrency is temporary and will be fixed or if b) Unapplied methods are incompatible structured concurrency and I should find a different pattern for observing changes.
@MainActor func _main() async throws {
let db = DB()
let controller = XController(db: db)
controller.didLoad()
db.set("Hello")
print("exiting main")
}
try await _main()
@MainActor class XController {
let db: DB
init(db: DB) {
self.db = db
}
func didLoad() {
// Error: Call to main actor-isolated instance method 'observe(update:)' in a synchronous nonisolated context
// Warning: Calls to instance method 'observe(update:)' from outside of its actor context are implicitly asynchronous
db.subscribe(target: self, action: XController.observe)
}
func observe(update: String) {
print("observed \(update)")
}
}
@MainActor class DB {
var value: String = ""
weak var target: XController?
var action: ((XController) -> (String) -> Void)?
func subscribe(target: XController, action: @escaping (XController) -> (String) -> Void) {
self.target = target
self.action = action
}
func broadcast() {
guard let target else { action = nil; return }
action?(target)(value)
}
func set(_ value: String) {
self.value = value
broadcast()
}
}