__attribute__((noescape)) In Swift

Is there a way to add a __attribute__((noescape)) to a parameter in Swift? I'm running into an issue with an Objective-C method that requires it.

In a large mixed project, I'm overriding UITableView.performBatchUpdates(_:completion) in a UITableView subclass in Swift.:

class MyCustomTableView: UITableView {
   // how to annotate the updates parameter as NS_NOESCAPE?
   public override func performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) {
        // do some bookkeeping
       super.performBatchUpdates(updates, completion: completion)
   } 
}

This subclass is also used in the Objective-C parts of the project, so it get's added to the Project-Swift.h header and Swift automatically converts it to the following:

@interface MyCustomTableView : UITableView
- (void)performBatchUpdates:(void (^ _Nullable)(void))updates completion:(void (^ _Nullable)(BOOL))completion;
@end

However, Objective-C requires that this method is annotation with NS_NOESCAPE ( = __attribute__((noescape))).

I don't see a direct way to add this in Swift? How do I solve this?

Swiss has this limitation: you can't have a closure non-escaping and optional at the same time, Optional<()->Void> is always escaping. With non optional closures (not your case) you just use @escaping to make the closure escaping and by default the closure is non escaping.

Are you getting some compilation error or what?

We're getting warnings (which we also cannot turn off) in the Project-Swift.h generated header file, because Objective-C requires an NS_NOESCAPE.

If you don't fear missing it in other possibly important cases, you may switch that warning off by adding -Wno-missing-noescape to Xcode's "Other Warning Flags".

Yes we don’t wanna do that. So there’s no swift solution for this?

Not sure, possibly no pure Swift solution.

As you are using Obj-C anyway, as a workaround you may create an obj-c wrapper:

Wrong code
@interface UITableView(Extras)
- (void)performBatchUpdates2:(void (^ _Nullable)(void))updates completion:(void (^ _Nullable)(BOOL finished))completion;
@end

@implementation UITableView(Extras)
- (void)performBatchUpdates2:(void (^ _Nullable)(void))updates completion:(void (^ _Nullable)(BOOL finished))completion {
    [self performBatchUpdates:updates completion:completion];
}
@end

and override performBatchUpdates2 instead of performBatchUpdates from Swift. You'll get no warning in this case.

Edit: no, that won't work, scratch that.

This will work, a sketch:

Obj-C:

@interface ObjcTableView: UITableView
-(void)performBatchUpdates2: ...
@end

@implementation ObjcTableView
-(void)performBatchUpdates: ... {
    [self performBatchUpdates2: ...];
}
-(void)performBatchUpdates2: ... {
    [super performBatchUpdates: ...];
}
@end

Swift:

class MyCustomTableView: ObjcTableView {
    override func performBatchUpdates2(...) {
        ....
    }
}

Or just live with the warning!

1 Like