玩转RN:IOS如何导出原生模块并在js中调用

3,260 阅读2分钟

写在前面

React Native 的宗旨是『Learn once, write anywhere.』,换句话说,在 RN 开发中,大多数时候,开发者并不需要关心 native 那一层,安心编写react组件以及相应的业务逻辑就可以了。

不过有的时候,也会需要用到原生的模块,比如:

  • 高性能计算:图片处理、文件压缩等;
  • 复用native已有的模块:比如跨Android、IOS的链接库等;
  • RN 尚不支持的native模块:比如iOS SDK更新吼,RN可能还没有对应的模块;

这种情况下,可以利用 RN 导出原生模块给 js 调用,下文会简单举例说明。文中例子可以在 笔者的Github 上找到,也建议查看官方文档获取更详细说明。

自定义原生模块

首先,创建头文件 TodoList.h,TodoList 实现了 RCTBridgeModule 协议。

// TodoList.h
#import <React/RCTBridgeModule.h>

@interface TodoList : NSObject <RCTBridgeModule>

@end

接着,创建 TodoList.m。代码简单解释下:

  • RCT_EXPORT_MODULE();:将 TodoList 模块导出;
  • RCT_EXPORT_METHOD(add):导出 add 方法,:后是参数列表(可多个);
// TodoList.m
#import "TodoList.h"
#import <React/RCTLog.h>

@implementation TodoList

NSMutableArray *list = nil;

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(create)
{
    list = [[NSMutableArray alloc] init];
}

RCT_EXPORT_METHOD(add:(NSString *)item)
{
    NSLog(@"add: %@", item);
    [list addObject:@{ @"desc":item, @"done": @0 }];
}

从js中调用

从 js 中调用自定义的原生模块非常简单,代码如下:

import {NativeModules} from 'react-native';

const TodoList = NativeModules.TodoList;
TodoList.create();
TodoList.add('起床');

xcode中打印日志:

2019-11-08 21:24:32.636547+0800 RNTest[5027:50749999] add: 起床

函数回调

在前端开发中,函数回调非常常见,RN 中导出的原生方法,也支持传入回调方法,如下所示。

// 例子:方法导出,支持回调
RCT_EXPORT_METHOD(addWithCallback:(NSString *)item callback:(RCTResponseSenderBlock)callback)
{
    NSLog(@"addWithCallback: %@", item);
    
    [list addObject:@{ @"desc":item, @"done": @0 }];
    callback(@[[NSNull null], list]);
}

在 index.js 中新增调用:

// 例子:函数回调
TodoList.addWithCallback('吃早餐', (error, list) => {
  if (error === null) {
    console.log(`[addWithCallback] list.length == ${list.length}`);
  }      
});

输出如下:

[addWithCallback] list.length == 2

事件触发

调用原生模块,除了函数回调,Native Module 还可以主动抛出事件,在 js 层进行监听处理,例子如下。

首先,对 TodoList.h 进行修改,继承 RCTEventEmitter

// TodoList.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface TodoList : RCTEventEmitter <RCTBridgeModule>

@end

接着,导出 addAndTriggerEvent 方法,里面主动抛出 ItemAdded 事件。注意,通过 sendEventWithName 抛出的事件需要在 supportedEvents 里进行注册,不然运行期会报错。

// TodoList.m

// 返回的数组为支持的事件名列表
- (NSArray<NSString *> *)supportedEvents
{
    return @[@"ItemAdded"];
}

// 例子:事件抛出
RCT_EXPORT_METHOD(addAndTriggerEvent:(NSString *)item)
{
    NSLog(@"addAndTriggerEvent: %@", item);
    
    [list addObject:@{ @"desc":item, @"done": @0 }];
    [self sendEventWithName:@"ItemAdded" body:list];
}

最后,在 index.js 监听 ItemAdded 事件,搞定。

const TodoList = NativeModules.TodoList;
const todoListEmitter = new NativeEventEmitter(TodoList);

TodoList.create();

// 监听事件
todoListEmitter.addListener('ItemAdded', list => {
  console.log(`[ItemAdded] list.length == ${list.length}`);
});

// 添加item,在这个方法里会抛出事件
TodoList.addAndTriggerEvent('上班');

输出如下:

[ItemAdded] list.length == 3

返回Promise实例

对于异步操作来说,返回 Promise 实例通常是个不错的选择,RN原生函数 也进行了支持了这个特性。

首先,导出方法 addAndReturnPromise,这个方法接收两个参数 item、isResolved,根据 isResolved 的值决定 Promise 实例最终的状态。

// 例子:返回Promise实例
RCT_REMAP_METHOD(addAndReturnPromise,
                 item:(NSString *) item
                 ifResolved:(int) ifResolved
                 findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"addAndTriggerEvent: %@", item);

    [list addObject:@{ @"desc":item, @"done": @0 }];
    
    if (ifResolved) {
        resolve([NSString stringWithFormat:@"%@ is accepted.", item]);
    } else {
        NSString *domain = @"com.chyingp.www";
        NSDictionary *userInfo = @{ @"desc": @"nonsense" };
        NSInteger code = -1;
        NSError *error =[NSError errorWithDomain:domain code:code userInfo:userInfo];
        NSString *errMsg = [NSString stringWithFormat:@"%@ is not accepted.", item];
        
        reject(@"ErrorFromNativeModule", errMsg, error);
    }
}

其次,在 index.js 中添加调用

// 例子:返回Promise实例
const resolver = msg => console.log(`[addAndReturnPromise] [resolved] ${msg}`);
const rejecter = error => console.log(`[addAndReturnPromise] [rejected] ${error.message}`);
    
TodoList.addAndReturnPromise('加班', true).then(resolver).catch(rejecter);
TodoList.addAndReturnPromise('休息', false).then(resolver).catch(rejecter);

输出如下(有点悲伤):

[addAndReturnPromise] [resolved] 加班 is accepted.
[addAndReturnPromise] [rejected] 休息 is not accepted.

相关链接

2019.11.07-calling-native-module-from-js-in-rn/

facebook.github.io/react-nativ…