-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Swift support in native modules #207
Comments
@mhart - My understanding is that any Swift support would need to be a community driven effort, given that Facebook uses Objective C internally at the moment. Until this changes there's probably no need to have an issue tracking this, we have enough open issues already 😄 If you'd like to lead this effort it would definitely be welcome! Feel free to create another repo and get in touch in IRC. |
Damn 😿 I'm surprised it's not on your roadmap! |
Same here. Especially considering the fact that in case some not-yet-implemented native functionality is needed, it would be much easier for a JS developer to learn Swift than Objective-C (which at least at the first glance looks like a mess) |
Not to derail the topic at hand but for context: there are lots of little things about Swift that make Objective-C the better choice for now. The tooling for Swift is premature in that build times are worse, every app that uses Swift adds ~6MB to its binary for the runtime, Xcode crashes frequently, the Swift compiler produces code with bugs, the language is changing in backwards-incompatible ways, etc. Apple will fix these issues but it will be a year or two before developing with Swift is better than developing with Objective-C. 2c: my experience learning the languages was that Objective-C was actually easier since it's a relatively thin layer above C and is a rather dynamic language. It also works well with all the iOS APIs whereas sometimes there are interop issues with Swift. So generally speaking, I expect developers to have a better time writing native React modules in Objective-C at this time. That all said, eventually supporting Swift probably makes sense as it improves and exploring how Swift could export native methods to JS would be useful. |
Dissapointing.. |
We won't be using Swift internally within the framework for the foreseeable future for all the reasons that @ide mentions, however we would welcome support for writing modules and views in Swift. The reason this is difficult at the moment is because we're heavily reliant on macros for setting up the bindings between Objective-C code and the JavaScript bridge, and Swift has no support for macros whatsoever. I'm not particularly a fan of macros in general, but it makes this process simple and elegant to an extent that would be hard (if not impossible) to achieve any other way, and crucially it isolates the syntax from the implementation, giving us freedom to make changes to the infrastructure without constantly breaking everyone's existing code. To support modules written in 100% swift, we would need to provide a totally separate mechanism for registering the module class, methods and properties because writing the bridging code that the Obj-C macros generate manually in Swift would be cumbersome and fragile (assuming that it's even possible). In the future, once the APIs settle, we may be able to move to a more traditional protocol-based approach, which would be better suited to Swift. In the meantime though, if you don't mind writing a little bit of glue code in Objective-C, it's perfectly possible to implement the bulk of your native module logic in Swift if you wish to do so. |
It is possible to utilise Swift without too much work. Because you solely need to generate some metadata for React Native you can get away with just creating nearly blank categories, though it does require at least 1 Objective-C implementation file per Swift class. ( No header or #import necessary! ) @ide @mhart @brentvatne @randomer @frankbolviken https://gist.github.com/robertjpayne/855fdb15d5ceca12f6c5 Note that this implementation is pretty fragile and relies on a bit of knowledge about how react-native currently generates it's metadata for the bridging. @nicklockwood is there any chance you'd be open to maintaining a set of Swift compatible macros? I'm just not sure how much of a moving target you expect the current macros to be. |
@robertjpayne Very cool! Would be great to have a better home for this for people to follow if you do indeed want to keep it up to date (like a dedicated repo). I'm sure that would get quite popular 😄 But totally get that might be too much work if things are shifting rapidly. I mean, ideally, Facebook would just add this sort of thing to their docs somewhere. |
@robertjpayne Nice! 👍 |
Yea I thought about a repo but given it's only the single header file it seems excessive. I'll wait and see if @nicklockwood chimes in about the moving target. |
@robertjpayne I feel ya – I think the README would be larger than the actual code 😃, but that's what people would be interested in I'd say. I'd be fine with a gist if GitHub actually made them easy to follow and manage comments... |
How about adding this to react-native-cli as a command to add a Swift class to an existing app?
|
Reopening this as it seems to be a useful place to track this discussion. |
@nicklockwood - sounds good, seemed stagnant for a while but the discussion has really picked up since I closed the issue, go figure! 😄 |
@robertjpayne awesome work - I appreciate you taking the initiative with this. I agree that this seems like Swift module support should be part of the core rather than something external because it will be very hard to ensure macros are kept in sync. Your RCT_SWIFT_EXPORT_MODULE macro can be simplified to:
I thought it could be reduced to just
But it seems that with this approach, module_name isn't registered as conforming to RCTBridgeModule, which is a little strange (though probably easy to fix in the bridge logic itself). |
@nicklockwood the first interface is solely there to declare to Objective-C that the class exists since it's implemented in Swift. It's essentially just the public header file. To add the protocol conformance it has to be done inside the category. Though theoretically the user could add the protocol in Swift. |
The RCT_SWIFT_EXPORT_MODULE_PUSH/POP syntax is quite elegant, but it has shades of trying to reinvent the Objective-C language, which we've been trying to avoid (macros are seductive). I'm going to think about this for a bit and see if I can come up with something less obtrusive. |
@robertjpayne I tried both those code examples before posting them. It seems to work without the extra category. |
@nicklockwood right! I agree about the macros, since there's less interface boilerplate it may not be necessary to have the PUSH/POP but just make the user define the interface, category and then add the export methods inside the category definition. |
I was thinking that the RCT_SWIFT_EXPORT_METHOD macro could be called something like RCT_EXTERN_METHOD instead (meaning "this method is declared elsewhere"). Then we could implement RCT_EXPORT_METHOD in terms of RCT_EXTERN_METHOD:
That way, we avoid duplicating the macro logic, which should make it easier to maintain. |
Re: class definition - I get the need for the category but think you could get rid of the first @interface declaration and pull in the swift bridging header instead. |
@ide but then you'd have to add the class to the bridging header, right? This way, you don't have to, as the Swift class doesn't need to reference any symbols in SwiftReactModuleExports.m |
@nicklockwood oops - I meant generated header instead of the bridging header. It's just |
@ide I've never had good luck with the generated headers, the tooling for them is buggy, it's much more reliable to generate the a small interface stub to work with. |
@robertjpayne have you had issues with the latest Xcode? Swift got a lot more tenable in the last release and a lot of compiler issues went away. |
@ide yup, with my simple example I've tried just now and it refuses to generate a header for the class (Xcode 6.3.1). I think probably because the class is not referenced -anywhere- so the Swift compiler is probably stripping it. |
Boo Apple. OK - the @interface stub sounds good to me. |
@nicklockwood I'll work on cleaning this up best I can so we don't use more macro magic than necessary. It's going to look something like: @interface MySwiftModule : NSObject <RCTBridgeModule>
@end
@implementation MySwiftModule (RCTBridgeModuleExterns)
RCT_EXTERN_MODULE(MySwiftModule)
RCT_EXTERN_METHOD(printMessage:(NSString *)message)
RCT_EXTERN_REMAP_METHOD(printMessage, printMessage:(NSString *)message)
@end We could deduce this entirely into just the interface declaration if we're ok asking users to add |
We could derive the js name from the category name, but that might be a little too magical :-) |
Another pattern I considered is
|
This would have the benefit of generating all the boilerplate for you, and it feels a bit less magical than the push/pop to me. That may be purely subjective on my part though. What do you think? |
Yup I like that idea I'll try and get something working in the next few hours.
|
Only thing is we still have to get the user to specify superclass for the stubbed interface. I can't find a way around that unless I can reliably get generated headers to work.
|
True. Maybe something more like this then? @interface RCT_EXTERN_MODULE(MyModule, NSObject) RCT_EXTERN_METHOD(foo); |
@nicklockwood how does this look? #define RCT_EXTERN_MODULE(objc_name, objc_supername) \
RCT_EXTERN_REMAP_MODULE(objc_name, objc_name, objc_supername)
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
objc_name : objc_supername \
@end \
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
@end \
@implementation objc_name (RCTExternModule) \
RCT_EXPORT_MODULE(js_name)
#define RCT_EXTERN_METHOD(method) \
RCT_EXTERN_REMAP_METHOD(, method)
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
- (void)__rct_export__##method { \
__attribute__((used, section("__DATA,RCTExport"))) \
__attribute__((__aligned__(1))) \
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \ Allows two ways to make them: Custom Module/Method Name: @interface RCT_EXTERN_REMAP_MODULE(AwesomeJS, SwiftReactModule, NSObject)
RCT_EXTERN_REMAP_METHOD(doAwesome, printMessage:(NSString *)message)
@end var AwesomeJS = require('NativeModules').AwesomeJS;
AwesomeJS.doAwesome("Hello World"); Inferred Module/Method Name: @interface RCT_EXTERN_MODULE(SwiftReactModule, NSObject)
RCT_EXTERN_METHOD(printMessage:(NSString *)message)
@end var SwiftReactModule = require('NativeModules').SwiftReactModule;
SwiftReactModule.printMessage("Hello World"); |
@nicklockwood unfortunately we have to redefine the method macro contents because the current Best option would be to have these extern method macro contain the "header logic" and the non-extern methods macro contain the extern macro + implementation logic. |
@robertjpayne yep, this looks good to me. Yeah, I realised we'd have to reimplement RCT_EXPORT_METHOD by having it call RCT_EXTERN_METHOD (see my earlier comment), but I think that's not a problem if RCT_EXTERN_METHOD is going to be part of the core. |
@nicklockwood cool, well if you're happy with this I'll go ahead and whip up a PR and remap RCT_EXPORT_METHOD to use RCT_EXTERN_METHOD. |
@robertjpayne good call on RCT_EXTERN_REMAP_MODULE as well - I like the consistency there. I'll run this by the rest of the team today, but I'm pretty happy with this solution overall. |
@robertjpayne please do :-) |
Nice work ppl! 🎉 So I guess this can be closed now thanks to b72acc2 ? |
(you all knew this was coming 😸 )
The current documentation suggests Swift is not yet supported, but I thought it would be good to have an issue to at least track progress on that, if it is indeed coming?
There are some related issues here, but I don't think any of them tackle Swift support at a high level:
The text was updated successfully, but these errors were encountered: