ReactNative native module development and release--iOS

ReactNative native module development and release--iOS

[[166181]]

I made a ReactNative App some time ago, and found that many components in ReactNative did not exist, so I still needed to write native modules myself to let JS call them. It was precisely because of many problems encountered in this writing process that I found many deficiencies in the official website documents. So I came up with the idea of ​​writing a practical tutorial, and finally I had this article.

This article mainly uses writing a native module as an example to explain some of the knowledge we use in writing native modules. In the whole example, it is equipped with complete practical code to facilitate everyone's understanding and debugging. In addition to these contents, the article also describes how we can publish our own native modules to npm to share them with others. I hope it can be helpful to everyone, and I also hope that everyone will share their own native modules.

Example code github address: https://github.com/liuchungui/react-native-BGNativeModuleExample

Preparation:

Create a ReactNative project

We need to create a ReactNative project first. Use the following command to create it.

  1. react native init TestProject

After creating the project, we use xcode to open the iOS project under TestProject/ios/.

Create a static library and manually link it to the project

First, we create a folder react-native-BGNativeModuleExample in the node_modules of the ReactNative project we created earlier, and then we create an ios folder under the newly created folder.

  1. $ cd TestProject/node_modules
  2. $ mkdir react- native -BGNativeModuleExample
  3. $ cd react- native -BGNativeModuleExample
  4. $ mkdir ios

Then, since ReactNative components are all static libraries, we need to create a static library if we publish it to npm for others to use. We use Xcode to create a static library named BGNativeModuleExample. After creating it, we copy all the files in the created static library to the node_modules/react- native-BGNativeModuleExample/ios directory.

The iOS file directory is as follows:

  1. |____BGNativeModuleExample
  2. | |____BGNativeModuleExample.h
  3. | |____BGNativeModuleExample.m
  4. |____BGNativeModuleExample.xcodeproj

Finally, we need to manually link this static library into the project.

1. Use Xcode to open the created static library, add a line of Header Search Paths with the value of $(SRCROOT)/../../react-native/React and set it to recursive.

2. Drag the BGNativeModuleExample static library project to the Library in the project.

3. Select TARGETS => TestProject => Build Settings => Link Binary With Libraries and add the static library libBGNativeModuleExample.a

At this point, our preparations are complete. We have a purpose for doing this, which is to simulate the process of npm linking, establish the environment, and avoid the problem that others cannot find the static library after publishing it to npm.

1. Writing native module code

1. Create native modules

Select the BGNativeModuleExample static library we created, and then import RCTBridgeModule.h in the BGNativeModuleExample.h file to make the BGNativeModuleExample class conform to the RCTBridgeModule protocol.

  1. //The contents of the BGNativeModuleExample.h file are as follows  
  2. # import # import   "RCTBridgeModule.h"  
  3. @interface BGNativeModuleExample : NSObject @end  

In the BGNativeModuleExample.m file, we need to implement the RCTBridgeModule protocol. To implement the RCTBridgeModule protocol, our class needs to include the RCT_EXPORT_MODULE() macro. This macro can also add a parameter to specify the name of the module in Javascript. If not specified, the class name will be used by default.

Here, we specify the name of the module as BGNativeModuleExample.

  1. RCT_EXPORT_MODULE(BGNativeModuleExample);

After implementing the RCTBridgeModule protocol, we can get the native module we created in js as follows.

  1. import { NativeModules } from 'react-native' ;
  2. var BGNativeModuleExample = NativeModules.BGNativeModuleExample;

It should be noted that the parameter passed by the RCT_EXPORT_MODULE macro cannot be a string in OC. If @"BGNativeModuleExample" is passed, the module name we export to JS is actually @"BGNativeModuleExample", and BGNativeModuleExample cannot be found. Here, we can actually find the native module we created by printing NativeModules.

2. Add methods to native modules

We need to explicitly declare the methods to be exported to JS, otherwise ReactNative will not export any methods. The declaration is implemented through the RCT_EXPORT_METHOD() macro:

  1. RCT_EXPORT_METHOD(testPrint:(NSString *)name info:(NSDictionary *)info) {
  2. RCTLogInfo(@ "%@: %@" , name, info);
  3. }

In JS, we can call this method like this:

  1. BGNativeModuleExample.testPrint( "Jack" , {
  2. height: '1.78m' ,
  3. weight: '7kg'  
  4. });

3. Parameter Type

RCT_EXPORT_METHOD() supports all standard JSON types, including:

  • string (NSString)

  • number (NSInteger, float, double, CGFloat, NSNumber)

  • boolean (BOOL, NSNumber)

  • array (NSArray) contains any type in this list

  • map (NSDictionary) contains keys of type string and values ​​of any type in this list

  • function(RCTResponseSenderBlock)

In addition, any type supported by the RCTConvert class can be used (see RCTConvert for more information). RCTConvert also provides a series of helper functions that receive a JSON value and convert it to a native Objective-C type or class.

To learn more, please click Native Modules.

4. Callback function

Warning: This section is currently in an experimental stage, as we don't have much practical experience dealing with callback functions.

There is a warning about callback function in the official documentation, but no problem has been found in the use process. In OC, we add a getNativeClass method to call back the class name of the current module to JS.

  1. RCT_EXPORT_METHOD(getNativeClass:(RCTResponseSenderBlock)callback) {
  2. callback(@[NSStringFromClass([self class ])]);
  3. }

In JS, we get the class name of the native module in the following way

  1. BGNativeModuleExample.getNativeClass(name => {
  2. console.log( "nativeClass: " , name);
  3. });

Native modules should usually only call callbacks once. However, they can save the callback and call it in the future. This is most common when encapsulating iOS APIs that get return values ​​through "delegate functions".

5. Promises

Native modules can also use promises to simplify code, and the effect is better when paired with the ES2016 (ES7) standard async/await syntax. If the last two parameters of the bridge native method are RCTPromiseResolveBlock and RCTPromiseRejectBlock, the corresponding JS method will return a Promise object.

We use Promises to determine whether the native module will respond to the method. If it responds, it returns YES. If it does not respond, it returns an error message. The code is as follows:

  1. RCT_REMAP_METHOD(testRespondMethod,
  2. name:(NSString *)name
  3. resolver:(RCTPromiseResolveBlock)resolve
  4. rejecter:(RCTPromiseRejectBlock)reject) {
  5. if ([self respondsToSelector:NSSelectorFromString(name)]) {
  6. resolve( @YES );
  7. }
  8. else {
  9. reject(@ "-1001" , @ "not respond this method" , nil);
  10. }
  11. }

In JS, we have two ways to call, the first is through then....catch:

  1. BGNativeModuleExample.testRespondMethod( "dealloc" )
  2. .then(result => {
  3. console.log( "result is " , result);
  4. })
  5. . catch (error => {
  6. console.log(error);
  7. });

The second method is called through try...catch. Compared with the first method, the second method will report a warning "Possible Unhandled Promiss Rejection (id:0)".

  1. async testRespond() {
  2. try {
  3. var result = BGNativeModuleExample.testRespondMethod( "hell" );
  4. if (result) {
  5. console.log( "respond this method" );
  6. }
  7. } catch (e) {
  8. console.log(e);
  9. }
  10. }

Note: If we do not need parameters when using Promiss, just remove the name line in OC; if multiple parameters are required, just add an extra line under name. Note that no commas are required between them.

6. Multithreading

The module we operate here does not involve UI, so we create a serial queue for it to use, as follows:

  1. return dispatch_queue_create( "com.liuchungui.demo" , DISPATCH_QUEUE_SERIAL);

Note: Sharing dispatch queues between modules

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

For more details on thread operations, please refer to: http://reactnative.cn/docs/0.24/native-modules-ios.html#content

7. Export constants

Native modules can export constants that are always available on the JavaScript side. This method can be used to pass static data, avoiding a round trip through the bridge.

In OC, we implement the constantsToExport method as follows:

  1. - (NSDictionary *)constantsToExport {
  2. return @{ @ "BGModuleName" : @ "BGNativeModuleExample" ,
  3. TestEventName: TestEventName
  4. };
  5. }

In JS, we print this constant

  1. console.log( "BGModuleName value is " , BGNativeModuleExample.BGModuleName);

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

8. Send events to JS

Even if it is not called by JS, the local module can send event notifications to JS. The most direct way is to use eventDispatcher.

Here, in order to receive events, we start a timer and send an event every second.

  1. # import   "BGNativeModuleExample.h"  
  2. # import   "RCTEventDispatcher.h"  
  3. @implementation BGNativeModuleExample
  4. @synthesize bridge = _bridge;
  5. - (instancetype)init {
  6. if (self = [ super init]) {
  7. [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector: @selector (sendEventToJS) userInfo:nil repeats:YES];
  8. }
  9. return self;
  10. }
  11. - ( void )receiveNotification:(NSNotification *)notification {
  12. [self.bridge.eventDispatcher sendAppEventWithName:TestEventName body:@{@ "name" : @ "Jack" }];
  13. }
  14. @end  

In JS, we receive events like this

  1. NativeAppEventEmitter.addListener(BGNativeModuleExample.TestEventName, info => {
  2. console.log(info);
  3. });

Note: When writing OC code, you need to add @synthesize bridge = _bridge;, otherwise the error Exception -[BGNativeModuleExample brige]; unrecognized selector sent to instance will be reported when receiving events.

The above native code is written, mainly based on code practice to make up for some deficiencies in the official documentation. If you need to know more about native module encapsulation, you can refer to the native module or the official source code.

2. Release and launch

After we write the native module according to the above steps, we will publish the native module we wrote to npm.

1. We need to create a github repository

Create a repository react-native-BGNativeModuleExample on GitHub, and then link it to the react-native-BGNativeModuleExample directory we created earlier

  1. $ cd TestProject/node_modules/react- native -BGNativeModuleExample
  2. $ git init .
  3. $ git remote add origin https: //github.com/liuchungui/react-native-BGNativeModuleExample.git  

2. We need to create the entry file of the native module

We need to create an index.js in the react-native-BGNativeModuleExample directory, which is the entry point of the entire native module. We just export the native here.

  1. //index.js  
  2. import React, { NativeModules } from 'react-native' ;
  3. module.exports = NativeModules.BGNativeModuleExample;

3. Publish to npm

Before publishing to npm, we need to create a package.json file, which contains all the information of the module, such as name, version, description, dependencies, author, license, etc. We use the npm init command in the root directory of react-native-BGNativeModuleExample to create package.json. The system will prompt us to enter the required information. If you don't want to enter it, just press Enter to skip it.

  1. $ npm init
  2. This utility will walk you through creating a package .json file.
  3. It only covers the most common items, and tries to guess sensible defaults.
  4. See `npm help json` for definitive documentation on these fields
  5. and exactly what they do .
  6. Use `npm install --save` afterwards to install a package and
  7. save it as a dependency in the package .json file.
  8. Press ^C at any time to quit.
  9. name: (react- native -BGNativeModuleExample)

After the input is completed, the system will ask us to confirm whether the content of the file is correct. If there is no problem, just enter yes, and the package.json will be created. The package.json file I created here is as follows:

  1. {
  2. "name" : "react-native-nativemodule-example" ,
  3. "version" : "1.0.0" ,
  4. "description" : "" ,
  5. "main" : "index.js" ,
  6. "scripts" : {
  7. "test" : "echo \"Error: no test specified\" && exit 1"  
  8. },
  9. "repository" : {
  10. "type" : "git" ,
  11. "url" : "git+https://github.com/liuchungui/react-native-BGNativeModuleExample.git"  
  12. },
  13. "author" : "" ,
  14. "license" : "ISC" ,
  15. "bugs" : {
  16. "url" : "https://github.com/liuchungui/react-native-BGNativeModuleExample/issues"  
  17. },
  18. "homepage" : "https://github.com/liuchungui/react-native-BGNativeModuleExample#readme"  
  19. }

If the native module we write depends on other native modules, we need to add dependencies in package.json. Since we don't have any related dependencies here, we don't need to add them:

  1. "dependencies" : {
  2. }

After initializing package.json, we can publish it to npm.

If you don't have an npm account, you need to register an account. This account will be added to the local npm configuration and used to publish modules.

  1. $ npm adduser
  2. Username: your name
  3. Password: your password
  4. Email : [email protected]

After success, npm will store the authentication information in ~/.npmrc, and you can view the user currently used by npm with the following command:

  1. $ npm whoami

After completing the above, we can publish it.

  1. $ npm publish
  2. + [email protected]  

At this point, we have successfully published the module to npmjs.org. Of course, don't forget to publish our code to GitHub.

  1. $ git pull origin master
  2. $ git add .
  3. $ git commit -m 'add Project'  
  4. $ git push origin master

Sometimes, some files don't need to be published, such as Example files, we can ignore them through .npmignore. For example, the content of my .npmignore file is as follows:

  1. Example/
  2. .git
  3. .gitignore
  4. .idea

In this case, when we publish it with npm, Example will not be published to npm.

4. Add Example, test whether it is available, and add README

We create an Example ReactNative project in the react-native-BGNativeModuleExample directory and install the react-native-nativemodule- example module we released through the rnpm install react-native-nativemodule-example command.

  1. $ rnpm install react- native -nativemodule-example
  2. TestProject @0 .0. 1 /Users/user/github/TestProject
  3. └── react- native -nativemodule-example @1 .0. 0  
  4. rnpm-link info Linking react- native -nativemodule-example ios dependency
  5. rnpm-link info iOS module react- native -nativemodule-example has been successfully linked
  6. rnpm-link info Module react- native -nativemodule-example has been successfully installed & linked

The above prompts that the installation and link are successful, and we can use it in js.

  1. import BGNativeModuleExample from 'react-native-nativemodule-example' ;
  2. BGNativeModuleExample.testPrint( "Jack" , {
  3. height: '1.78m' ,
  4. weight: '7kg'  
  5. });

5. We also need to write a README file after the release.

The README file is very important. If there is no README file, others will not know what our native component is used for. Therefore, it is necessary to add a README file, which needs to tell others what our native component is used for, how to install it, the API, the user manual, etc.

6. Native module upgrade and new version release

When we add new code or fix bugs, we need to release a new version. We only need to modify the version value in the package.json file and then use npm publish to publish it.

Summarize

This article is mainly divided into two parts. The first part is about writing native modules, and the second part is about publishing what we wrote to npm.

refer to

How to publish Node modules to the NPM community

Native modules

<<:  Who are the top ten highest-paid technology companies in the world that are envied and hated?

>>:  Implementing image recognition in Web development based on Google Vision API and Ionic

Recommend

New Android virus may force you to change your phone

Security company Lookout recently issued a warnin...

Six JavaScript frameworks that developers should know

With the rapid development of the Internet, mobil...

iOS A Song of Ice and Fire – Using XPC to pass the sandbox

0x00 Sequence Ice refers to user state, and fire ...

WeChat 8.0.6 iOS version is officially launched with these 6 major changes

In actual testing, although there are no major fu...

Across 4,600 kilometers! Space-ground integrated quantum communication

The space-ground integrated quantum communication...

How to promote an event well?

With the rapid development of various industries,...