Swift Concurrency in Real Apps
Consider this code, wherein we create a custom
NSTableColumn
that uses an image instead of aString
as its header.
Holly Borla posted a fix that special-cases NSObject.init()
:
Now, overriding
NSObject.init()
within a@MainActor
-isolated type is difficult-to-impossible, especially if you need to call an initializer from an intermediate superclass that is also@MainActor
-isolated. Standard opt-out tools likeMainActor.assumeIsolated
cannot be applied to things like stored property initialization andsuper.init()
, making the issue extremely difficult to work around. This is a major usability regression for programs that interoperate with Objective-C and make heavy use of@MainActor
-isolated types.[…]
I can’t reproduce a failure by overriding
viewDidAppear
in a@MainActor
-isolated subclass ofUIViewController
, but if this is happening for methods that are marked asnonisolated
or otherwise aren’t annotated with@MainActor
, then that is a deliberate result of my original change. Overriding superclass methods and changing isolation does risk a data-race, which is why I made the original change. If you believe the superclass method is incorrectly annotated, that is a framework problem that you should report via Apple Feedback Assistant.
(As it turns out,
UINib
actually isMainActor
-isolated, whileNSNib
is not. There’s one person out there somewhere maintaining a cross-platform app that uses nibs and this is going to make their life hell.)The whole point of Nibs is to serialize UI components. That’s literally the only reason it exists. And that doesn’t work in Swift 6[…]
[…]
The compiler must honor the API contract of
awakeFromNib
. The framework maintainers have decided to not apply any isolation. The types and APIs aren’t able to fully describe the concurrent behavior, but the developer knows what’s actually going to happen at runtime.This is a special-case of a very common issue and is exactly why dynamic isolation exists.
[…]
Some APIs, even really important ones that you use every day, just won’t ever work well with Swift concurrency. I’m certain some language changes could make things a little easier here and there. But, largely, these problematic APIs are going to be deprecated and replaced. It’s happening with Notifications right now.
Pretty sure there are use cases for background threads. I think code for printing uses
NSView
, and usually involves a background op.
TIL:
awakeFromNib()
can be called more than once when objects are loaded from XIBs.IDK, maybe that is something that should be mentioned in the documentation.
Previously:
- SwiftUWhy
- Watch Out for Counterintuitive Implicit Actor-Isolation
- Swift Concurrency Proposal Index
- Problematic Swift Concurrency Patterns
- Swift Concurrency and Objective-C
- Unwanted Swift Concurrency Checking
- Swift 6
3 Comments RSS · Twitter · Mastodon
> IDK, maybe that is something that should be mentioned in the documentation.
What I do know is that this is a well known fact since the introduction of view-based tableviews like more than a decade ago. as you could end up going through the awakeFromNib method of the main controller if you were not cautious.
Having to write workaround code like this, looks pretty insane:
override func awakeFromNib() {
guard Thread.isMainThread else {
DispatchQueue.main.sync(execute: awakeFromNib)
return
}
MainActor.assumeIsolated {
super.awakeFromNib()
doSomething()
}
}
--
This shit is not for app developers.
In the case where you are loading a bag of data on a background thread, which is to be passed to the main thread when done...if you have a method like awakeFromNib (put finishing touches on the loaded data), doing this:
guard Thread.isMainThread else {
DispatchQueue.main.sync(execute: awakeFromNib)
return
}
Looks a lot worse to me than letting the method execute on the current thread. If you reroute stuff to other threads here and there, dispatch_sync here and there to appease the compiler you could be placing land mines all over the place.