ReactNative Native Modules in Swift – Part 3 (details, conclusion, …)

To give a conclusion to this subject treated in Part1 and Part2, I want to go back to how native module work and how to communicate between native modules and ReactNative modules.

First, let’s talk about the .m file that enables to link native to JS thru runtime. The code was this :

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyNativeModule, NSObject)
RCT_EXTERN_METHOD(triggerJSRequest)
@end

What this code does is exactly this (for those who want to understand what is behind the macros). “RCT_EXTERN_MODULE” does this :

MyNativeModule : NSObject
@end

@interface MyNativeModule (RCTExternModule) <RCTBridgeModule>
@end

@implementation MyNativeModule (RCTExternModule)
dispatch_queue_t RCTJSThread;
void RCTRegisterModule(Class);
+ (NSString *)moduleName {
    return @"MyNativeModule";
}
+ (void)load {
    RCTRegisterModule(self);
}

and each time you use “RCT_EXTERN_METHOD” it does this :

+ (const RCTMethodInfo *)__rct_export__uniqueID {
    static RCTMethodInfo config = {"triggerJSRequest", "triggerRequest", NO};
    return &config;
}

__rct_export__” is a token used to parse all the methods inside a native class and bind them to JS using a unique Id generated with __LINE__ and __COUNTER__.

Another thing that should be noticed if you have a swift function like this :

    @objc func call(name: String) -> Void {
        print("call : " + name)
    }

and you want to bridge it to ReactNative, then it should be written like this in your .m file :

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyNativeModule, NSObject)
RCT_EXTERN_METHOD(callWithName:(NSString *)name)
@end

where “WithName” is a replacement for “call(name“. (If you didn’t want to have this “WithName“, then you should write your Swift function like this : @objc func call(_ name: String) -> Void. The “_” enables to avoid the “WithName“).

And you can add as many functions as needed of course, like this :

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyNativeModule, NSObject)
RCT_EXTERN_METHOD(callWithName:(NSString *)name)
RCT_EXTERN_METHOD(triggerAnotherRequest)
@end

to call them in your JS, just do this :

React.NativeModules.AnotherNativeModule.triggerAnotherRequest();
React.NativeModules.AnotherNativeModule.callWithName("Fred");

Now let’s talk about the Swift file associated to the native module. This is related to this code :

import Foundation
import React
@objc(MyNativeModule)
class MyNativeModule: NSObject {    
    @objc func triggerRequest() -> Void {
        print("trigger Request")
    }
}

which is the minimal form it can take. In our case, this will generate a warning in the debugger when running the app : “Module MyNativeModule requires main queue setup since it overrides `init` but doesn’t implement `requiresMainQueueSetup`. In a future release React Native will default to initializing all native modules on a background thread unless explicitly opted-out of.
This warning can be silenced by adding this static method “requiresMainQueueSetup” in the Swift file :

    @objc static func requiresMainQueueSetup() -> Bool {
        return false
    }

In fact everything is described in the protocol RCTBridgeModule and if you wanted to implement everything in this protocol, you should have this in your file :

import Foundation
import React

@objc(MyNativeModule)
class MyNativeModule: NSObject {
// MARK: RCTBridgeModule
    @objc static func requiresMainQueueSetup() -> Bool {
        return false
    }
    @objc var bridge: RCTBridge?
    @objc var methodQueue: DispatchQueue?
    @objc func batchDidComplete() {
        // do something if needed at the end of the batch
    }
    @objc func partialBatchDidFlush() {
        // do something if needed at the end of the partial flush of the batch
    }
// MARK: Bridged methods
    @objc func triggerRequest() -> Void {
        print("trigger Request")
    }
}

the “bridge” variable is very important if you want to communicate with what created the native module, because the module is only created once by a bridge. And then you start to understand the process of communication between ReactNative and Native Module. ReactNative uses ReactNative.NativeModules, this creates a bridge, the bridge at runtime tries to find the requested module, if it finds it then it creates it and if the creation works then it tries to find the requested method. It does all that at runtime and not at compilation time… I will make another post on how to use the bridge to emit events from Native Module to ReactNative thru the use of EventEmitter. The others methods and variables in this file are out of the scope of this post but I will treat them in some other posts later.

I hope this post helped few people to understand better how ReactNative and Native Module in iOS/Swift are bridged.

Have a very nice day. Best regards.

Fred.

You can find all of this on github : https://github.com/fredfoc/ReactNativePart1and2

One thought on “ReactNative Native Modules in Swift – Part 3 (details, conclusion, …)”

Leave a Reply

Your email address will not be published. Required fields are marked *