• React Native填坑之旅--与Native通信之iOS篇


    终于开始新一篇的填坑之旅了。RN厉害的一个地方就是RN可以和Native组件通信。这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是。自定义视图的使用会在后面讲到。

    坑是什么样的坑

    主要的是遇到一个业务需求,需要检测当前应用的版本是什么。需要返回当前的版本号和build数。

    主要的需求在native来说非常简单:

        NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
        NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
    

    两句分别获得了版本号和build数。

    开始填坑

    填坑其实也是意外的简单。当然,我们不准备把这个代码作为库发布到npm上给别人用,所以复杂度自然降低了不少。

    首先、在Xcode里创建RNUpgrade类作为后面和RN通信的native组件。这会在项目里创建两个objc的文件RNUpgrade.hRNUpgrade.m

    RNUpgrade.h头文件中,添加RCTBridgeModule协议。要给RN暴露接口这个协议是必须的。

    #import <Foundation/Foundation.h>
    #import "RCTBridgeModule.h"
    
    @interface RNUpgrade : NSObject<RCTBridgeModule>
    
    @end
    

    之后对于头文件就可以什么都不用管了。至少对于暴露接口这件事是这样的。

    下面就来看源文件吧。

    看文档,要暴露native方法就必须在源文件里包含一个宏的调用,这个宏是:RCT_EXPORT_MODULE()。这个宏可以包含一个参数指定RN中访问这个模块的名字。默认的就是你的objc类的名字。

    #import "RNUpgrade.h"
    #import "RCTUtils.h"
    #import "AppDelegate.h"
    
    NSString *const RNUPGRADE_ERROR_DOMAIN = @"Upgrade info error";
    
    @implementation RNUpgrade
    
    RCT_EXPORT_MODULE();
    
    @end
    

    那么如何来暴露出一个方法呢?使用RCT_EXPORT_METHOD()宏。官网的例子:

    RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
    { 
        RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
    }
    

    RCT_EXPORT_METHOD的参数就是这个方法的声明部分,方法体在外面。RCT_EXPORT_METHOD(someMethod:(NSString*)stringParameter)这样的,然后外面写方法体。
    那么,我要返回现在APP的版本信息就可以写成这样:

    RCT_EXPORT_METHOD(getCurrentInfo) {
      @try {
        NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
        NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
        return @{@"versionName": version, @"versionCode": build}
      } @catch (NSException *exception) {
          //Log error info...
      }
    }
    

    但是,如何返回字典呢?直接return?接着差文档。

    暴露给RN的方法是不能直接返回任何东西的。因为RN的调用时异步的,所以只能使用回调的方式,或者触发事件的方式实现返回值。

    回调!看个官网的例子:

    RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
    { 
        NSArray *events = ... 
        callback(@[[NSNull null], events]);
    }
    

    好的,回调就说到这里了。因为笔者的项目已经上了async/await了,回调就显得没啥必要了。而且,文档显示。RN也提供了暴露接口返回Promise的支持。只需要在方法里接受两个参数,一个resolver,一个rejecter

    RCT_EXPORT_METHOD(getCurrentInfo:(RCTPromiseResolveBlock)resolve
                      rejecter:(RCTPromiseRejectBlock)reject) {
      @try {
        NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
        NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
        resolve(@{@"versionName": version, @"versionCode": build});
      } @catch (NSException *exception) {
        NSError *error = [NSError errorWithDomain:RNUPGRADE_ERROR_DOMAIN code:1 userInfo: exception.userInfo];
        reject(exception.name, exception.reason, error);
      }
    }
    

    于是,这样就可以返回一个Promise了。

    在RN的项目里调用这个方法:

    // 首先通过`NativeModules`接收暴露的native模块。
    import { NativeModules } from "react-native"
    const upgrade = NativeModules.RNUpgrade
    
    // 方法调用
    const ret = await Promise.all([upgrade.getCurrentInfo(), upgrade.getUpgradeInfo()])
    

    没错,模块还有另外一个native方法。这个native方法也返回一个Promise

    返回声明相同的native方法

    其实在native模块里很多方法的声明都是一模一样的:resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject。因为我只需要接收一个resolverrejecter以便返回一个Promise。于是就用到了RN提供的另外一个宏:RCT_REMAP_METHOD。这个宏专门用来处理声明基本一样的情况。它会把native里的声明基本一样的宏映射到一个唯一的RN方法名称上。

    RCT_REMAP_METHOD(getCurrentInfo,
                      resolver:(RCTPromiseResolveBlock)resolve
                      rejecter:(RCTPromiseRejectBlock)reject) {
      @try {
        NSString * version = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"];
        NSString * build = [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey];
        resolve(@{@"versionName": version, @"versionCode": build});
      } @catch (NSException *exception) {
        NSError *error = [NSError errorWithDomain:RNUPGRADE_ERROR_DOMAIN code:1 userInfo: exception.userInfo];
        reject(exception.name, exception.reason, error);
      }
    }
    

    基本上在项目里如何暴露一个native方法给RN的js调用非常简单,就如上面所述一样。

    1. 在头文件里继承了RCTBridgeModule协议。
    2. 在源文件里使用RCT_EXPORT_MODULE();宏。
    3. 使用宏RCT_EXPORT_METHOD暴露方法。
      如果方法需要返回值的话使用回调、或者Promise。这也只是native方法写几个参数的问题。

    重要的一点:线程

    在文档中有这么一点:多线程。千万不要根据RN实现的一些细节就假设你的模块运行在某某线程上。官网也说了,这个是会变的。如果你要确定你的代码运行在什么线程上,通过方法- (dispatch_queue_t)methodQueue来指定。

    注意:指定的methodQueue会被你模块里的所有方法共享。

    如果运行在主线程上:

    - (dispatch_queue_t)methodQueue
    { 
        return dispatch_get_main_queue();
    }
    

    如果运行在自己创建的线程上:

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

    如果模块里只有小部分代码运行在其他的线程上,可以使用native里传统的方法dispatch_async来实现:

    RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 在这里执行长时间的操作 ... 
            // 你可以在任何线程/队列中执行回调函数 
            callback(@[...]);
        });
    }
    

    而且:methodQueue
    方法会在模块被初始化的时候被执行一次,然后会被React Native的桥接机制保存下来,所以你不需要自己保存队列的引用

    省心省力!

    填坑完毕!

  • 相关阅读:
    Navsion二次开发_学习笔记
    《软件开发者路线图:从学徒到高手》笔记
    Concurrency并发性
    Excel VBA 函数
    在excel worksheet中添加button 和对Excel workbook做权限控制相关的新知识
    outline (group) 在Excel worksheet 中
    自主学习进度(软件工程)
    四则运算实现2(JAVA)
    简单四则运算实现(JAVA)
    数学建模(Lingo)(非线性整数规划)
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/6141372.html
Copyright © 2020-2023  润新知