RN与原生交互(OC)

2022-05-03  本文已影响0人  今年27

有时候App需要访问平安API, 单在ReactNative中没有相应的模块,或者你需要复用一些Java代码, 不想用JavaScript再重新实现一边;有或者你需要实现一些高性能的,多线程代码,譬如图片处理,数据库,或者一些高级扩展等等。
ReactNative可以与原生平台进行交互

开发iOS原生模块的主要流程
1.编写原生模块的iOS代码
2.暴露接口与数据交互
3.导出ReactNative原生模块

为了暴露接口以及进行数据交互我们需要借助ReactNativeReact/RCTBridgeModule.h来操作

#import <React/RCTBridgeModule.h>

@interface AISBridgeModule : NSObject<RCTBridgeModule>

@end
#import "AISBridgeModule.h"

@implementation AISBridgeModule
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();//让RN在主线程回调这些方法
}
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSInteger result=num1+num2;
    resolve([NSString stringWithFormat:@"%ld",(long)result]);//回调JS
}
RCT_EXPORT_METHOD(sendMessage:(NSDictionary*)params){//接受RN发过来的消息
    [[NSNotificationCenter defaultCenter] postNotificationName:@"sendMessage" object:params];
}
@end

RCT_EXPORT_MODULE:这个方法不传参数,则采用类名作为导出的模块名,如果传参则采用参数名为模块名

RCT_EXPORT_METHOD:主要用来声明需要暴露的接口,被标注的方法支持如下数据类型

string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array(NSArray) 包含本列表任意类型
object(NSDictionary) 包含string类型的键与本列表中任意类型的值
function (RCTResponseSenderBlock)

原生模块向JS传递数据我们可以介乎CallBacksPromise

CallBacks

原生模块支持一个特殊类型的参数CallBacks,我们可以通过它来对js进行回调,告诉js调用原生模块方法的结果。

原生
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 success:(RCTResponseSenderBlock)success
                  failure:(RCTResponseErrorBlock)failure){
    NSInteger result=num1+num2;
  success(@[[NSString stringWithFormat:@"%ld",(long)result]]);
}

JS调用
const modules = NativeModules.AISBridgeModule
 modules.doAdd(parseInt(this.num1), parseInt(this.num2),(error, result) => {
  if ( error) {
     console.log(error);
  } else {
    console.log(result);
 }
, ()=>{                          
})

Promises:

如果我们暴露的方法最后一个参数是Promise,那么JS调用它的时候将会返回一个Promise

原生
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSInteger result=num1+num2;
    resolve([NSString stringWithFormat:@"%ld",(long)result]);//回调JS
}

JS
const modules = NativeModules.AISBridgeModule
 modules.doAdd(parseInt(this.num1), parseInt(this.num2)).then(e => {
   this.setState({
      result: e
   })
})

另外我们也可以用ES2016的async/await语法来简化我们的代码

async doAdd(){
  const modules = NativeModules.AISBridgeModule
  var result = await modules.doAdd(parseInt(this.num1), parseInt(this.num2))
}

提示:另外要告诉大家的是无论Callbacks还是Promise,我们都只
能调用一次,也就是”you call me once, I can only call you once“

接下来我为大家介绍一种原生模块可以多次向js传递数据的方式

RCTEventEmitter

向JS发送事件
在原生模块中我们可以向js发送多次时间,即使原生模块么有被直接调用。

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

@interface DataToJSPresenter : RCTEventEmitter <RCTBridgeModule>

@end

#import "DataToJSPresenter.h"

@implementation DataToJSPresenter
- (NSArray<NSString *> *)supportedEvents
{
    return @[@"testData"];
}
- (instancetype)init {
    if (self = [super init]) {//在module初始化的时候注册fireData广播
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fireData:) name:@"fireData" object:nil];
    }
    return self;
}
- (void)fireData:(NSNotification *)notification{//发送数据给RN
    NSString *eventName = notification.object[@"name"];
    NSDictionary *params = notification.object[@"params"];
    [self sendEventWithName:eventName body:params];
}

@end

事件调度名
-(NSArray<NSString *> *)supportedEvents
发送事件
-(void)sendEventWithName:(NSString *)eventName body:(id)body
eventName就是我们要发送的事件名,params是此次事件所带的数据,接下来我们就可以在js模块中监听这个事件了

UNSAFE_componentWillMount() {
        // console.log("bridge", JSBridge);
        console.log("AISBridgeModule", NativeModules.AISBridgeModule)
        // const modules = NativeModules.AISBridgeModule
        this.testDataListener = DeviceEventEmitter.addListener('testData', e => {//for Android
            this.setState({
                data: e.data
            })
        });
        this.dataToJSPresenter = new NativeEventEmitter(NativeModules.DataToJSPresenter);
        console.log("dataToJSPresenter", this.dataToJSPresenter)

        this.dataToJSPresenter.addListener('testData', (e) => {// for iOS
            console.log("testData", e);
            this.setState({
                data: e.data
            })
        })
    }

不要忘记在组件被卸载的时候移除监听

    componentWillUnmount() {
        if (this.testDataListener) {
            this.testDataListener.remove();
        }
        if (this.dataToJSPresenter){
            // this.dataToJSPresenter.removeListener('testData');
            this.dataToJSPresenter.removeAllListeners
        }
    }

这个是android端的事件发送器代码

private void sendEvent(ReactContext, String eventName, @Nullable WritableMap params){
  reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(event, params)
}

关于线程:

ReactNative在一个独立串行的GCD队列中调用原生模块的方法,在我们为ReactNative开发原生模块的时候,如果有耗时操作,比如:文件的读写,网络操作等,我们需要新开辟一个线程,不然的话,这些耗时操作会阻塞JS线程。通过实现方法- (dispatch_queue_t)methodQueue,原生可以指定自己在哪个队列中被执行。具体来说如果调用一些必须在主线程中才能使用的API,

- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();//让RN在主线程回调这些方法
}

如果操作需要花费很长时间,则应道声明一个用于执行操作的独立队列。举个历史RCTAsyncLocalStorage模块创建了一个自己的queue,这样它在做一些较慢的磁盘操作的时候就不会阻塞React本身的消息队列:

- (dispatch_queue_t)methodQueue
{
  return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_CONCURRENT);
}

指定的methodQueue会被模块里的所有方法功效,如果你的方法只有一个好事较长的(或者由于某种原因必须在不同队列中运行的), 你可以在函数体内用dispatch_async方法来在另一个队列执行,而不影响其他方法

上一篇下一篇

猜你喜欢

热点阅读