Passing non-sendable parameter 'completion' to function expecting a @Sendable closure

I'm using Swift 6. The issue happens when Swift calls ObjC.

@interface Bar : NSObject

- (void)doSomethingWithCompletionHandler:(nullable void (^)(void))completion;

@end

- (void)doSomethingWithCompletionHandler:(void (^)(void))completion {
    if (completion) {
        completion();
    }
}
class Zar {
    func doSomething(completionHandler: (() -> Void)?) {
        completionHandler?()
    }
}
@objc public final class DismissHandler: NSObject {
    public func dismissViewController(completion: (() -> Void)?) {
        let bar = Bar()
        bar?.doSomething(completionHandler: completion) // <- Passing non-sendable parameter 'completion' to function expecting a @Sendable closure
        
        let zar = Zar()
        zar.doSomething(completionHandler: completion) // <- No error here
    }
}

Why error on calling ObjC bar but not Swift zar?

When importing methods from Objective-C, the compiler understands something about how they're conventionally used, including that completion handlers are generally called from different concurrent contexts and therefore need to be @Sendable (and @escaping).

When you write your own method in Swift, the compiler expects you to put the right attributes on your methods. In this case, you've declared the method as taking a non-@Sendable, non-@escaping function. This works because you've given the method a toy implementation that always uses the completion handler immediately. If you gave it a more realistic implementation — one which, presumably, invokes the completion handler asynchronously after some event occurs — you will find that it needs to be at least @escaping and almost certainly also @Sendable.

1 Like

Isn't optional closure automatically @escaping?

As to @Sendable, the truth is all my code is on main queue so the closure doesn't need to be Sendable. I tried marking them so but still got the same error:

NS_SWIFT_UI_ACTOR
@interface Bar : NSObject

- (void)doSomethingWithCompletionHandler:(nullable void (^)(void))completion;

@end

- (void)doSomethingWithCompletionHandler:(void (^)(void))completion {
    if (completion) {
        completion();
    }
}
@MainActor
class Zar {
    func doSomething(completionHandler: (() -> Void)?) {
        completionHandler?()
    }
}
@MainActor
@objc public final class DismissHandler: NSObject {
    public func dismissViewController(completion: (() -> Void)?) {
        let bar = Bar()
        bar?.doSomething(completionHandler: completion) // <- Passing non-sendable parameter 'completion' to function expecting a @Sendable closure
        
        let zar = Zar()
        zar.doSomething(completionHandler: completion) // <- No error here
    }
}

If you are sure that your completion doesn't need to be sendable you can mark it as such

- (void)doSomethingWithCompletionHandler:(nullable void (^)(void))completion NS_SWIFT_NOT_SENDABLE;

Oh yes, I missed that it was optional.

If you promise to invoke that completion handler on the main actor, then sure, you can mark it with NS_SWIFT_UI_ACTOR.

1 Like

Or, if you can make sure the closure will always be called in the same isolation domain, you can just mark this single parameter not being @Sendable:

- (void)doSomethingWithCompletionHandler:(nullable void (^ NS_SWIFT_NONSENDABLE )(void)) completion ;

I already tried NS_SWIFT_UI_ACTOR but it doesn't solve the problem. Please check the code I shared in my previous reply to you.

Thank you and @antdon for the NS_SWIFT_NONSENDABLE trick, it works! However, I'm still wondering why NS_SWIFT_UI_ACTOR doesn't help.

1 Like

I meant on the parameter. The class being @MainActor doesn’t mean it promises to run its completion handlers there.

Making the completion handler non-sendable when the class is main-actor ends up having the same effect because the concurrent context it can’t be sent from is the main actor, thus it must be main-actor-isolated.

I also tried - (void)doSomethingWithCompletionHandler:(nullable void (NS_SWIFT_UI_ACTOR ^)(void))completion but still got the same error.