Annotate Objective-C Block Type with NS_SWIFT_UI_ACTOR

Hi folks,

I'm trying to update some Objective-C code to be friendlier with respect to Swift Concurrency. In this case, I have a protocol definition like this:

@import Foundation;

typedef void (^FooAction)(void);

@protocol Foo
@property (nonatomic, nullable) FooAction didDoFoo;
- (void)doFoo;
@end

I have a Swift class in another module that needs to be able to conform to that protocol. In this case, I need to attribute FooAction with @MainActor to express the guarantees of the callers from Objective-C.

class Bar: NSObject, Foo {
    var didDoFoo: FooAction?

    func `do`() {
        Task {
            await didDoFoo?()
        }
    }
}

No matter where I try attributing NS_SWIFT_UI_ACTOR to the FooAction typedef, if I create an instance of Bar and try setting didDoFoo, I get errors that "converting function value...loses global actor 'MainActor'". Is this interaction between Objective-C and Swift supported, and if so, what is the proper way to attribute an Objective-C typedef for the main actor?

Currently, that's only recognized on parameters, but putting it on the typedef would be extremely reasonable, and it's probably the only reasonable way we could express that the property getter returns a @MainActor function. I've filed [SR-15885] NS_SWIFT_UI_ACTOR isn't recognized on block typedefs - Swift to track this.

I don't think there's a pure-Swift workaround that doesn't look totally awful, but you can put helper functions in your bridging header that work within the limits of what the import logic can handle:

static inline void setDidDoFoo(_Nonnull id<Foo> foo,
                               _Nullable FooAction action NS_SWIFT_UI_ACTOR) {
  foo.didDoFoo = action;
}

NS_SWIFT_UI_ACTOR
static inline void callDidDoFoo(_Nonnull id<Foo> foo) {
  FooAction action = foo.didDoFoo;
  if (action) action();
}
1 Like

Thank you John. I'm actually relieved to hear it's just an oversight. I'll make a note of this in my team's documentation.