Welcome to Native Modules for iOS. Please start by reading the Native Modules Intro for an intro to what native modules are.
Create a Calendar Native Module
In the following guide you will create a native module,
To get started, open up the iOS project within your React Native application in Xcode. You can find your iOS project here within a React Native app:
We recommend using Xcode to write your native code. Xcode is built for iOS development, and using it will help you to quickly resolve smaller errors like code syntax.
Create Custom Native Module Files
The first step is to create our main custom native module header and implementation files. Create a new file called
and add the following to it:
You can use any name that fits the native module you are building. Name the class
RCTCalendarModule since you are creating a calendar native module. Since ObjC does not have language-level support for namespaces like Java or C++, convention is to prepend the class name with a substring. This could be an abbreviation of your application name or your infra name. RCT, in this example, refers to React.
As you can see below, the CalendarModule class implements the
RCTBridgeModule protocol. A native module is an Objective-C class that implements the
Next up, let’s start implementing the native module. Create the corresponding implementation file,
RCTCalendarModule.m, in the same folder and include the following content:
For now, your
RCTCalendarModule.m native module only includes a
RCT_EXPORT_MODULE macro, which exports and registers the native module class with React Native. The
This argument is not a string literal. In the example below
RCT_EXPORT_MODULE(CalendarModuleFoo) is passed, not
The native module can then be accessed in JS like this:
Let's follow the example below and call
RCT_EXPORT_MODULE without any arguments. As a result, the module will be exposed to React Native using the name
CalendarModule, since that is the Objective-C class name, with RCT removed.
The native module can then be accessed in JS like this:
RCT_EXPORT_METHOD macro. Methods written in the
RCT_EXPORT_METHOD macro are asynchronous and the return type is therefore always void. In order to pass a result from a
CalendarModule native module using the
RCT_EXPORT_METHOD macro. Call it
createCalendarEvent() and for now have it take in name and location arguments as strings. Argument type options will be covered shortly.
Please note that the
RCT_EXPORT_METHODmacro will not be necessary with TurboModules unless your method relies on RCT argument conversion (see argument types below). Ultimately, React Native will remove
RCT_EXPORT_MACRO,so we discourage people from using
RCTConvert. Instead, you can do the argument conversion within the method body.
Before you build out the
RCTLog APIs from React. Let’s import that header at the top of your file and then add the log call.
You can use the
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD to create a synchronous native method.
The return type of this method must be of object type (id) and should be serializable to JSON. This means that the hook can only return nil or JSON values (e.g. NSNumber, NSString, NSArray, NSDictionary).
At the moment, we do not recommend using synchronous methods, since calling methods synchronously can have strong performance penalties and introduce threading-related bugs to your native modules. Additionally, please note that if you choose to use
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD, your app can no longer use the Google Chrome debugger. This is because synchronous methods require the JS VM to share memory with the app. For the Google Chrome debugger, React Native runs inside the JS VM in Google Chrome, and communicates asynchronously with the mobile devices via WebSockets.
Test What You Have Built
Find a place in your application where you would like to add a call to the native module’s
createCalendarEvent() method. Below is an example of a component,
NewModuleButton you can add in your app. You can invoke the native module inside
NativeNodules from React Native:
You can then access the
CalendarModule native module off of
Now that you have the CalendarModule native module available, you can invoke your native method
createCalendarEvent(). Below it is added to the
onPress() method in
The final step is to rebuild the React Native app so that you can have the latest native code (with your new native module!) available. In your command line, where the react native application is located, run the following :
Building as You Iterate
npx react-native run-ios command.
You should now be able to invoke your
RCTLog in the function, you can confirm your native method is being invoked by enabling debug mode in your app and looking at the JS console in Chrome or the mobile app debugger Flipper. You should see your
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); message each time you invoke the native module method.
Beyond a Calendar Native Module
Better Native Module Export
Importing your native module by pulling it off of
NativeModules like above is a bit clunky.
Note this assumes that the place you are importing
CalendarModuleis in the same hierarchy as
CalendarModule.js. Please update the relative import as necessary.
|RCTResponseSenderBlock, RCTResponseErrorBlock||Function (failure)|
The following types are currently supported but will not be supported in TurboModules. Please avoid using them.
- Function (failure) -> RCTResponseErrorBlock
- Number -> NSInteger
- Number -> CGFloat
- Number -> float
For iOS, you can also write native module methods with any argument type that is supported by the
RCTConvert class (see RCTConvert for details about what is supported). The RCTConvert helper functions all accept a JSON value as input and map it to a native Objective-C type or class.
A native module can export constants by overriding the native method
The constant can then be accessed by invoking
getConstants() on the native module in JS like so:
Technically, it is possible to access constants exported in
constantsToExport() directly off the
NativeModule object. This will no longer be supported with TurboModules, so we encourage the community to switch to the above approach to avoid necessary migration down the line.
Note that the constants are exported only at initialization time, so if you change
For iOS, if you override
constantsToExport() then you should also implement
+ requiresMainQueueSetup:. If your module does not require access to UIKit, then you should respond to
+ requiresMainQueueSetup with NO.
For iOS, callbacks are implemented using the type
RCTResponseSenderBlock. Below the callback parameter
myCallBack is added to the
It is important to highlight that the callback is not invoked immediately after the native function completes—remember the communication is asynchronous.
A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later. This pattern is often used to wrap iOS APIs that require delegates— see
RCTAlertManager for an example. If the callback is never invoked, some memory is leaked.
There are two approaches to error handling with callbacks. The first is to follow Node’s convention and treat the first argument passed to the callback array as an error object.
Another option is to use two separate callbacks: onFailure and onSuccess.
RCTResponseErrorBlock argument, which is used for error callbacks and accepts an
NSError \* object. Please note that this argument type will not be supported with TurboModules.
async/await syntax. When the last parameter of a native module method is a
RCTPromiseRejectBlock, its corresponding JS method will return a JS Promise object.
Refactoring the above code to use a promise instead of callbacks looks like this:
await keyword within an async function to call it and wait for its result:
supportedEvents and call self
Update your header class to import
RCTEventEmitter and subclass
NativeEventEmitter instance around your module.
You will receive a warning if you expend resources unnecessarily by emitting an event while there are no listeners. To avoid this, and to optimize your module's workload (e.g. by unsubscribing from upstream notifications or pausing background tasks), you can override
stopObserving in your
Unless the native module provides its own method queue, it shouldn't make any assumptions about what thread it's being called on. Currently, if a native module doesn't provide a method queue, React Native will create a separate GCD queue for it and invoke its methods there. Please note that this is an implementation detail and might change. If you want to explicitly provide a method queue for a native module, override the
(dispatch_queue_t) methodQueue method in the native module. For example, if it needs to use a main-thread-only iOS API, it should specify this via:
Similarly, if an operation may take a long time to complete, the native module can specify its own queue to run operations on. Again, currently React Native will provide a separate method queue for your native module, but this is an implementation detail you should not rely on. If you don't provide your own method queue, in the future, you native module's long running operations may end up blocking async calls being executed on other unrelated native modules. The
RCTAsyncLocalStorage module here, for example, creates its own queue so the React queue isn't blocked waiting on potentially slow disk access.
methodQueue will be shared by all of the methods in your module. If only one of your methods is long-running (or needs to be run on a different queue than the others for some reason), you can use
dispatch_async inside the method to perform that particular method's code on another queue, without affecting the others:
Sharing dispatch queues between modules
methodQueuemethod will be called once when the module is initialized, and then retained by React Native, so there is no need to keep a reference to the queue yourself, unless you wish to make use of it within your module. However, if you wish to share the same queue between multiple modules then you will need to ensure that you retain and return the same queue instance for each of them.
React Native will create and initialize any registered native modules automatically. However, you may wish to create and initialize your own module instances to, for example, inject dependencies.
You can do this by creating a class that implements the
RCTBridgeDelegate Protocol, initializing an
RCTBridge with the delegate as an argument and initialising a
RCTRootView with the initialized bridge.
CalendarModule but as a Swift class:
It is important to use the
@objcmodifiers to ensure the class and functions are exported properly to the Objective-C runtime.
Then create a private implementation file that will register the required information with React Native:
For those of you new to Swift and Objective-C, whenever you mix the two languages in an iOS project, you will also need an additional bridging file, known as a bridging header, to expose the Objective-C files to Swift. Xcode will offer to create this header file for you if you add your Swift file to your app through the Xcode
File>New File menu option. You will need to import
RCTBridgeModule.h in this header file.
You can also use
Important when making third party modules: Static libraries with Swift are only supported in Xcode 9 and later. In order for the Xcode project to build when you use Swift in the iOS static library you include in the module, your main app project must contain Swift code and a bridging header itself. If your app project does not contain any Swift code, a workaround can be a single empty .swift file and an empty bridging header.
Reserved Method Names
Native modules can conform to the RCTInvalidating protocol on iOS by implementing the
invalidate() method. This method can be invoked when the native bridge is invalidated (ie: on devmode reload). Please use this mechanism as necessary to do the required cleanup for your native module.