-
-
Save siracusa/137e3fae8a384ac4bda8c56524826d6b to your computer and use it in GitHub Desktop.
NSWorkspace.shared.notificationCenter.addObserver( | |
forName: NSWorkspace.didLaunchApplicationNotification, | |
object: nil, queue: nil, using: { [weak self] notification in | |
self?.doStuff() | |
}) |
I think I got something working. I implemented an ObjC helper:
- (id <NSObject>)_addMainQueueObserverForName:(nullable NSNotificationName)name object:(nullable id)obj usingBlock:(void (NS_SWIFT_UI_ACTOR ^)(NSNotification *notification))block
{
return [self addObserverForName:name object:obj queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull notification) {
block(notification);
}];
}
But for whatever reason the NS_SWIFT_UI_ACTOR
wasn't coming through to the Swift side? I'll try to figure out a fix for that. But in the meantime, wrapping the ObjC helper above with a Swift-facing call that just calls through to it:
@MainActor @discardableResult
public func addMainActorObserver(forName name: Notification.Name, object obj: Any? = nil, using block: @escaping @MainActor (Notification) -> Void) -> any NSObjectProtocol {
return self._addMainQueueObserver(forName: name, object: obj, using: block)
}
Seems to work!
Oh well actually yes, you can achieve something similar in Swift:
extension NotificationCenter {
@MainActor
public func addMainActorObserver(
forName name: Notification.Name,
object obj: Any? = nil,
block: @escaping @MainActor (Notification) -> Void
) -> any NSObjectProtocol {
// this let's you lie to the compiler!
let sendableBlock = unsafeBitCast(block, to: (@Sendable (Notification) -> Void).self)
return self.addObserver(forName: name, object: obj, queue: .main, using: sendableBlock)
}
}
I'm not sure exactly what's up with the NS_SWIFT_UI_ACTOR
. I haven't looked into the clang annotations deeply but they are definitely supposed to work. Please file a bug if something isn't working as it should!
Perfect, thanks for the bitcast hack! And I'll look into the NS_SWIFT_UI_ACTOR
issue and file a bug if it's busted.
Also for anybody reading along: I think I decided I don't want/need the addMainActorObserver
to be @MainActor
. It's likely I'll only ever call it from the main thread, but because the method guarantees delivery on the main thread, it can be safely called from anywhere, I think.
I think this is correct! But, I also do want to point out that this version makes it possible to pass data unsafely across threads by way of the Notification
object. I don't think that's likely though.
Oh, right. That's a good point. Maybe I should leave it @MainActor
after all.
Glad to know I’m not missing something obvious, but yeah this is an unfortunate limitation. I wonder if some kind of nasty Swift bitcasting, or an ObjC helper method might be a workaround. This pattern is common enough in my code base that eliminating the clutter of all those “yes this is the main actor” assumeIsolated calls is very attractive.