RN calls native methods

1. Define the model

For React Native, an iOS NSObject that implements the RCTBridgeModule protocol and its subclasses can be called by JS.

Such as:

#import <React/RCTBridgeModule.h> @interface ZHJSManager : NSObject <RCTBridgeModule> @end #import "zhjsmanager. h" @implementation ZHJSManager // Export a module named ZHJSManager to JS by default RCT_EXPORT_MODULE(); // moduleName can also be customized by passing values such as RCT_EXPORT_MODULE(MyCustomJSManager); @endCopy the code

2. Exposure method

Once the model is defined, you can write the method. In order for RN to find the native method, you need to export (wrap) your method using another macro definition in.m: RCT_EXPORT_METHOD() :

@implementation ZHJSManager RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(methodNameWithFirst:(NSString *)paramStr second:(NSString *)second){//OC code You can also use RN to pass integers to the native side RCT_EXPORT_METHOD(getIntFromReactNative (NSInteger)count) {NSString * MSG = [NSString StringWithFormat :@" I received an integer from RN: %zd", count]; [self showAlert:msg]; }// You can even use RN to pass complex data such as nsDictionaries and NSArray to the native RCT_EXPORT_METHOD(getDictionaryFromRN: NSDictionary *)dict) { NSLog(@" dictionary passed from RN: %@", dict); }RCT_EXPORT_METHOD(getArrayFromRN: NSArray *)array) {NSLog(@" rn-passed array: %@", array); // Use the array arguments to do what you want, Native iOS siege lion} / / you can also use an RN block RCT_EXPORT_METHOD giveMyRNSomeStringWithBlock: (RCTResponseSenderBlock callbackBlock) {if (callbackBlock) {callbackBlock(@[@" string wrapped in array "]); / / /}} Use block back dictionary or array (wrapped into a two dimensional array) to RN RCT_EXPORT_METHOD giveMyRNSomeDicDataWithBlock: (RCTResponseSenderBlock callbackBlock) {if (callbackBlock) { NSDictionary *dict = @{@"key1": @"strValue", @"key2" : @(20), @"key3": @(YES)}; callbackBlock(@[dict]); RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString*, canReturnSomething){return @"SSSSS"; }// Use Promise to return data to RN end RCT_REMAP_METHOD(findEvents, findEventsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSArray *events = ... if (events) { resolve(events); } else { NSError *error = ... reject(@"no_events", @"There were no events", error); RCT_EXPORT_METHOD(usePromisePassToRN:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {// if (! [key isEqualToString:@""]) {// resolve(@(YES)); //} else {// reject(@"warning", @"key "cannot be empty!" , nil); // }//}@endCopy the code

3. Relevant JS code is implemented as follows

//Promise method call native async function updateEvents() {try {const Events = await ZHJSManager. FindEvents (); this.setState({ events }); } catch (e) { console.error(e); }} function methodNameWithFirst (param1, param2) {/ / other call ZHJSManager methodNameWithFirst (param1, param2); }Copy the code

4. Multithreaded related operations

Currently React Native calls Native module methods in a separate serial GCD queue. If you want to specify the queue, Native implements methods in your own module:

– (dispatch_queue_t)methodQueue. You can specify which queue you want to execute in.

Such as:

// Return main queue - (dispatch_queue_t)methodQueue {return dispatch_get_main_queue(); } // or other operations, You can return a different queue - (dispatch_queue_t)methodQueue {return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); }Copy the code

Note that after this method is implemented in your Module all methods are executed in the queue you specify.

If an operation takes a long time, the native module should not block, but declare a separate queue for the operation to be performed. For example, the RCTAsyncLocalStorage module creates its own queue so that it does slow disk operations without blocking the React message queue itself.

If “only” one of your methods is time-consuming (or has to run on a different queue for some reason), you can use the dispatch_async method in the body of the function to execute on another queue without affecting the other methods:

RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback) { Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // You can execute the callback function on any thread/queue (@[... ); }); }Copy the code

Note: Distribution queues are shared between modules

The methodQueue method is executed once when the module is initialized and is then saved by the React Native bridging mechanism, so you don’t need to save a reference to the queue yourself unless you want to use it elsewhere in the module. However, if you want several modules to share the same queue, you need to save and return the same queue instance yourself; Simply returning a queue with the same name is not sufficient.

Dependency Injection

Bridges in RN automatically register native modules that implement the RCTBridgeModule protocol, but you may also want to be able to initialize custom module instances yourself (so that dependencies can be injected).

To do this, you need to implement the RCTBridgeDelegate protocol, initialize the RCTBridge, and specify the proxy in the initialization method. Then initialize a RCTRootView using the initialized RCTBridge instance.

id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];

RCTRootView *rootView = [[RCTRootView alloc]
                        initWithBridge:bridge
                            moduleName:kModuleName
                     initialProperties:nil];
Copy the code

6. Export constants

Native modules can export constants that are readily accessible on the JavaScript side. By passing some static data in this way, you can avoid a back and forth interaction across the Bridge.

// Implement this method - (NSDictionary *)constantsToExport {return @{@"firstDayOfTheWeek": @"Monday"}; }Copy the code

The JavaScript side can access this data synchronously at any time:

console.log(CalendarManager.firstDayOfTheWeek);
Copy the code

However, note that this constant is exported only once during initialization, so even if you change the value returned by constantToExport at runtime, it will not affect the results in the JavaScript environment.

For constantToExport to take effect, you must also implement requiresMainQueueSetup and return True

/*
If you override - constantsToExport then you should also implement + requiresMainQueueSetup to let React Native know if your module needs to be initialized on the main thread. Otherwise you will see a warning that in the future your module may be initialized on a background thread unless you explicitly opt out with + requiresMainQueueSetup:
*/
+ (BOOL)requiresMainQueueSetup
{
  return YES;  // only do this if your module initialization relies on calling UIKit!
}
Copy the code

Call JS method (send event)

Native modules can send event notifications to JavaScript even if they are not called by JavaScript. The best way to do this is to inherit RCTEventEmitter, implement the suppportEvents method and call self sendEventWithName:.

/// The method of receiving notification. After receiving the notification, send the event to the RN end. After receiving an event, the RN can perform logical processing or jump to the interface. - (void)sendCustomEvent:(NSNotification *)notification {[self SendEventWithName: kCustomEventName body: @ "this is sent to RN the string"]; } /// override the method, - (NSArray<NSString *> *)supportedEvents {return @[kCustomEventName,kHaveImportListNotification,kHaveSelectedExpressImage,kHaveSelectedRbrsImages]; }Copy the code

Call native UI components

You can even write a component yourself in iOS and provide it to RN to render the page as part of Render

  • First create a subclass of RCTViewManager.

  • Add the RCT_EXPORT_MODULE() macro tag.

  • Implement the -(UIView *)view method.

    #import
    #import

    #import <React/RCTViewManager.h> @interface ZHMapManager : RCTViewManager @end

    @implementation ZHMapManager

    RCT_EXPORT_MODULE(ZHCustomMap)

    • (UIView *)view

    {// Properties such as frame and backgroundcolor are not allowed here. They will be overwritten by ReactNative, which ensures consistency across the page. // You can set style return [[MKMapView alloc] init]; } @end

To make js usable directly, add the mapview. js file, and then use a requireNativeComponent method of HOC provided by RN

// MapView.js import { requireNativeComponent } from 'react-native'; /* requireNativeComponent requireNativeComponent automatically resolves 'ZHCustomMap' to 'ZHCustomMapManager' But the suggestion is that this allows React Native's underlying framework to check whether Native attributes are consistent with those of the wrapper class, reducing the possibility of problems. */export default requireNativeComponent('ZHCustomMap',MapView); //Copy the code

Then you can use the MapView component in other components. so easy,so nice

// MyApp.jsimport MapView from './MapView.js'; . Render () {return <MapView style={{flex: 1}} />; }Copy the code

Here you might wonder: Each component has its own properties. How do native components set certain properties? Add export properties directly to our custom zhmapManager.m file

// ZHMapManager.m
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
Copy the code

Then we can happily use our native properties in our JS code

// MyApp.js
<MapView zoomEnabled={false} style={{ flex: 1 }} />
Copy the code

Of course, you should add some documentation to make it easier for users of your component, or if you’re using TS, make sure you include an interface.

More complex properties are as follows

// ZHMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
  [view setRegion:(json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region) animated:YES];
}
Copy the code