美文网首页
RN与原生交互(OC)

RN与原生交互(OC)

作者: 今年27 | 来源:发表于2022-05-03 21:35 被阅读0次

    有时候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方法来在另一个队列执行,而不影响其他方法

    相关文章

      网友评论

          本文标题:RN与原生交互(OC)

          本文链接:https://www.haomeiwen.com/subject/npciyrtx.html