Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift try inside Objective-C block

I need to create a function foo that takes a throwing closure as a parameter. I can implement it with either Swift or ObjC but I need to be able to call it from both.

Like this:

// Swift
func bar() throws
func foo(_ block: () throws -> void)

foo {
  try bar()
}

and

// Objc
[self foo:^(
  [other barBar];
)];

I tried to implement it with both Swift and ObjC without succes. With Swift:

@objc
func foo(block: () throws -> Void)

I get this error:

Method cannot be marked @objc because the type of the parameter 1 cannot be represented in Objective-C

If I try to implement it with ObjC:

typedef BOOL (^ThrowingBlock)(NSError **);
- (void)foo:(ThrowingBlock)block;

Then it does not translate to a block that throws (as it would with a function):

func foo(_: (NSErrorPointer) -> Bool)

Any idea how to achieve this?

like image 859
tgyhlsb Avatar asked Oct 16 '22 21:10

tgyhlsb


1 Answers

You could use the NS_REFINED_FOR_SWIFT macro to provide a unified interface between Objective-C and Swift, with throws in Swift and NSError ** in Objective-C.

From the Apple documentation:

You can use the NS_REFINED_FOR_SWIFT macro on an Objective-C method declaration to provide a refined Swift interface in an extension, while keeping the original implementation available to be called from the refined interface. For instance, an Objective-C method that takes one or more pointer arguments could be refined in Swift to return a tuple of values.

In your case you can declare foo as refined for Swift, and add a same method, in a class extension:

@interface MyClass : NSObject

- (void)foo:(void (^)(NSError **))block NS_REFINED_FOR_SWIFT;

@end

and in Swift:

extension MyClass {
    func foo(block: @escaping () throws -> Void) {
        // Objective-C's `foo` is now imported as `__foo`
        __foo { errPtr in
            do {
                try block()
            } catch {
                errPtr?.pointee = error as NSError
            }
        }
    }
}

Now you can call foo from both worlds, with the distinction that Objective-C code will need to pass a NSError ** block, while Swift callers can pass a nicer throws closure.

like image 86
Cristik Avatar answered Oct 21 '22 05:10

Cristik