Skip to content
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

[NativeModules] Support for loading React and/or native modules as a dynamic framework #1228

Closed
wants to merge 2 commits into from
Closed

[NativeModules] Support for loading React and/or native modules as a dynamic framework #1228

wants to merge 2 commits into from

Conversation

yusefnapora
Copy link
Contributor

This adds support for dynamically loading React as a framework. If accepted, closes #579. IntegrationTests all run successfully.

This introduces several changes to RCTBridge.m

  • extraction of strings from a mach_header section has been encapsulated in a helper class RCTHeaderStringExtractor.
  • instances of this class are stored in an NSSet and uniqued by the address of their associated mach_header.
  • a registration function RCTRegisterModuleProviderContainingAddress(const void *address) registers the binary containing a given address as a "module provider" and creates a RCTHeaderStringExtractor for the binary's mach_header. A convienience helper function RCTRegisterModuleProvider(void) does the same, but registers the caller instead of requiring an explicit address.
  • during the static RCTBridge initialization, all registered module providers are scanned, rather than just the binary in which RCTBridge is contained.

In RCTBridgeModule.h:

  • the RCT_EXPORT_MODULE macro now includes a definition for + (void) load that calls RCTRegisterModuleProvider - this allows registration of all module providers without changes to the existing api, but does potentially break native modules that already provide an implementation of load

A simple example project is a available at https://github.com/yusefnapora/react-dynamic-linking-example - it uses Cocoapods to install this branch with the use_frameworks! directive, which forces React to be compiled as a dynamic framework instead of a static library. React components are loaded from both the framework and the app binary.

Concerns:
My biggest problem with this approach is the load hook for native modules; any existing native modules that currently provide an implementation of + (void)load will refuse to compile. I chose this approach because it doesn't require any explicit registration of the binary containing the modules; when the class loader invokes load, that will happen automatically for all "module providers". As a result, this approach doesn't require "module providers" to know or care about whether they'll be linked dynamically or statically.

I also haven't measured the performance impact of the additional calls to load and RCTRegisterModuleProvider for each native module. I suspect it's minimal, as the only work involved is calling dladdr to obtain the mach header, and creation of a RCTHeaderStringExtractor, which is fairly lightweight. While a RCTHeaderStringExtractor is created for each invocation of RCTRegisterModuleProvider, only one is stored per mach header, as they are stored in an NSSet and hashed by the mach header address.

Perhaps a compromise to allow native modules to define a load hook would be to check whether the class responds to a (hypothetical) rct_load class method and, if so, call it from the macro-provided load method.

Please let me know if anything is unclear or I can do anything to help make this work.

This adds support for dynamically loading React as a framework.  It introduces several changes to RCTBridge.m

- extraction of strings from a mach_header section has been encapsulated in a helper class `RCTHeaderStringExtractor`.
- instances of this class are stored in an `NSSet` and uniqued by the address of their associated mach_header.
- a registration function `RCTRegisterModuleProviderContainingAddress(const void *address)` registers the binary containing a given address as a "module provider" and creates a `RCTHeaderStringExtractor` for the binary's mach_header.  A convienience helper function `RCTRegisterModuleProvider(void)` does the same, but registers the caller instead of requiring an explicit address.
- the RCT_EXPORT_MODULE macro now includes a definition for `+ (void) load` that calls `RCTRegisterModuleProvider` - this allows registration of all module providers without changes to the existing api, but does potentially break native modules that already provide an implementation of `load`
- during the static `RCTBridge` initialization, all registered module providers are scanned, rather than just the binary in which `RCTBridge` is contained.
@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 10, 2015
@nicklockwood
Copy link
Contributor

This is essentially the same as the solution I was planning, but thanks for doing the leg work!

The +load limitation is annoying, although in practice it can be worked around fairly easily (e.g. by adding custom load method inside a category).

@yusefnapora
Copy link
Contributor Author

Awesome, glad to help 😄

This adds another registration function
`RCTRegisterMainAppModuleProvider(void)` that attempts to register the
binary containing the `main()` function as a React Native module
provider.

This allows a project to link against a static library that contains a
React Native module, without itself providing any modules, or having to
explicitly register as a module provider.  Without this, the static
library containing the module is registered, but the `RCTExport`, etc.
strings are actually linked into the app binary and aren't found.
@yusefnapora
Copy link
Contributor Author

The last commit adds automatic registration of the binary containing the main() function, since I encountered an issue when trying to link against a static library containing a native module. Since my app binary didn't define any native modules, it was never being registered as a "module provider". The library was being registered, but the linker was relocating the __DATA sections for RCTExport, etc. to the app's binary, and they weren't being discovered.

@tptee
Copy link

tptee commented May 14, 2015

👍 super excited for this

@brentvatne
Copy link
Collaborator

@nicklockwood - any progress here? this looks like a nice feature 😄

@brentvatne brentvatne changed the title Support for loading React and/or native modules as a dynamic framework [NativeModules] Support for loading React and/or native modules as a dynamic framework May 31, 2015
@mebinum
Copy link

mebinum commented Jun 6, 2015

following this, will this mean I could create a Framework in swift and include in my project? At the moment I'm trying to do that and getting Class ... was not exported. Did you forget to use RCT_EXPORT_MODULE()?

@yusefnapora
Copy link
Contributor Author

@mebinum yes, this supports swift frameworks. My main motivation for this was to explore integrating react native into an existing swift app, where most of the code is contained in a framework that's shared between app and extension targets. Using this branch I was able to define React native modules in the framework and use them from an RCTRootView in either target (although react uses some cocoa apis that are off-limits to iOS app extensions).

@mebinum
Copy link

mebinum commented Jun 8, 2015

Awesome @yusefnapora look forward to this getting merged. Anyone know what the ETA on that is?

@yusefnapora
Copy link
Contributor Author

@nicklockwood I've been busy at work and haven't kept up with this, but it looks like the new module loading system supports dynamic linkage without the crazy hacks in this PR. 😸

Not sure exactly when the change was made, but I did a quick test on 0.7.0-rc.2. Modules defined in a shared framework load without a hitch, and React can be included as a pod using the use_frameworks! directive.

I'm going to close this and celebrate by poking at all the new features that have cropped up while I've been chained to Xcode. Excellent work, react team; the new module loading system is very nice and clean!

@yusefnapora yusefnapora closed this Jul 5, 2015
@colinhang
Copy link

I still ran into the same issues as @mebinum. @yusefnapora Is there an example that i can follow to see how to get it work? Thanks!

ayushjainrksh pushed a commit to MLH-Fellowship/react-native that referenced this pull request Jul 2, 2020
ryanlntn pushed a commit to ryanlntn/react-native that referenced this pull request Aug 9, 2022
…rsion

Ensure we use 0.68 for testing new apps, not 0.69 or later
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bridge] Native module inside framework
8 participants