简体   繁体   中英

NS_SWIFT_UI_ACTOR annotation not working with async variants of callback based objective-C methods

In Objective-c we can use NS_SWIFT_UI_ACTOR to indicate that a method or a whole class should be executed on the main actor (as we would do with @MainActor in Swift). This works correctly since, when invoking a method which returns a value from outside the main actor (using a Task.detached with background priority), the compiler forces us to call it using "await" (to allow for "actor switch") and, at runtime, I can see that the code is executed on the main thread.

However, when calling a callback based method on the same class (which gets an automatically "imported" async variant, as explained here ) the NS_SWIFT_UI_ACTOR seems not working properly, and the code is executed on a background thread.

NS_SWIFT_UI_ACTOR
@interface ObjcClass : NSObject
- (NSString *)method1;
- (void)method2WithCompletion: (void (^)(NSString * _Nonnull))completion;
@end
Task.detached(priority: .background) {
    let objcInstance = ObjcClass()
    print(await objcInstance.method1())   <----- this is called on the main thread
    print(await objcInstance.method2())   <----- this is called on a bg thread
}

While I understand what your expectation is I don't think as of now the way actor is implemented there is any way to resolve this. Actors in swift are reentrant, which means whenever you invoke any async method inside actor's method/actor task, actor suspends the current task until the method returns and starts processing other tasks. This reentrancy behavior is important as it doesn't just lock the actor when executing a long-running task.

Let's take an example of a MainActor isolated async method:

func test() async {
    callSync() // Runs on MainActor
    await callAsync() // Suspends to process other task, waits for return to resume executing on MainActor
    callSync() // Runs on MainActor
}

But since swift can't infer from an objective c async method which part should run on MainActor and for which part actor can just suspend the current task to process other tasks, the entire async method isn't isolated to the actor.

However, there are proposals currently to allow customizing an actor's reentrancy so you might be able to execute the entire objc async method on the actor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM