google/promises

Swift library using Promise, methods not visible in ObjC

fer662 opened this issue · 4 comments

I have a (private) swift pod which exposes a lot of methods that return Promise. I'm trying to use those methods from an ObjC codebase and they aren't visible from there. They do become visible if I use FBLPromise instead. I understand that's because Promise doesn't extend from NSObject, or something similar.
My idea for a workaround is making analog methods that call the underlying swift api and return the asObjCPromise() of them, and mark those methods with NS_SWIFT_UNAVAILABLE. Is there any better aproach or something i'm not seeing?

Edit: I missed the fact that NS_SWIFT_UNAVAILABLE is an ObjC thing. I also have a greater problem that is that these methods are part of a protocol, and providing alternatives doesn't fix the problem t hat the protocol cannot be seen from objc.
Am i to either fall back and use FBLPromises in my swift code, of always use my library from swift?

Hi Fernando,

To make any Swift func visible for ObjC you need an @objc annotation.
Your idea of a duplicate method that you can then expose for ObjC is the right one.
Here's the pattern to follow in your Swift pod:

@objc(MyObjCClass)
public class MyClass: NSObject {
  public func fooBar() -> Promise<String> {
    // ...
    return Promise("hello world")
  }
  
  @objc(objc_fooBar)
  public func fooBar() -> Promise<String>.ObjCPromise<NSString> {
    return fooBar().asObjCPromise()
  }
}

Then use it from an ObjC target:

#import "MySwiftPod-Swift.h"

FBLPromise<NSString *> *fooBarPromise = [[MyObjCClass new] objc_fooBar];

If you have a lot of such methods in your class, you may consider moving them into a separate extension just to make the code look cleaner, like so:

public class MyClass: NSObject {
  public func fooBar() -> Promise<String> {
    // ...
    return Promise("hello world")
  }
  // ...
}

// Objective-C interface.
public extension MyClass {
  @objc(objc_fooBar)
  public func fooBar() -> Promise<String>.ObjCPromise<NSString> {
    return fooBar().asObjCPromise()
  }
  // ...
}

Let us know if you have any further questions.
Thanks.

I'm using this approach, but can't see how to handle errors in objective c.

I use the following in swift:

Test.client.createUser(firstName: "firstname", lastName: "lastname").then { _ -> Void in
   assertionFailure("duplicate user created")
}.catch { error in
   assert(error is RequestError, "RequestError returned")
   let requestError = error as? RequestError
}

RequestError is a swift class visible in objective c

@objc(AGMRequestError)
public class RequestError

and in objective c

    [client createUserWithFirstName:@"firstname" withLastName:@"lastname"] .then(^id(AGAccessToken *accessToken) {
        XCTFail("duplicate user")
        return nil;
    })
     .catch(^(NSError *error) {
         AGMRequestError *requestError = (AGMRequestError *)error;
    });

error is of type _SwiftNativeNSError, not AGMRequestError, so I can't access any of its details

Hi Matt,

Given your RequestError has @objc annotation, I assume it already derives from NSObject? Any chance you derive it from NSError instead? Then you can create instances of RequestError in Swift as follows: RequestError(domain: "your domain", code: 42, userInfo: nil) or with a custom constructor, and cast to AGMRequestError in Objective-C.

By the way, you may also like declaring errors as enums in Swift and conforming to CustomNSError protocol to be able to extract domain, code and userInfo everywhere. Take a look at an example.

Thanks.

Hello,

Thank you for the suggestion. Yes RequestError derived from NSObject, and Error but I've changed it to just derive from NSError and that works - I can then cast to AGMRequestError in Objective-C.

I'll also consider conforming to CustomNSError instead. RequestError currently has a type, code, and description, but I could use userInfo for the type and description.