微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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,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的桥接机制保存下来,所以你不需要自己保存队列的引用**。

省心省力!

填坑完毕!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...