终于开始新一篇的填坑之旅了。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的桥接机制保存下来,所以你不需要自己保存队列的引用

省心省力!

填坑完毕!

React Native填坑之旅--与Native通信之iOS篇的更多相关文章

  1. React Native填坑之旅--Flow篇(番外)

    flow不是React Native必会的技能,但是作为正式的产品开发优势很有必要掌握的技能之一.所以,算是RN填坑之旅系列的番外篇. Flow是一个静态的检查类型检查工具,设计之初的目的就是为了可以 ...

  2. React Native填坑之旅--布局篇

    代码在这里: https://github.com/future-challenger/petshop/tree/master/client/petshop/src/controller 回头看看RN ...

  3. React Native填坑之旅--重新认识RN

    如同黑夜里的一道光一样,就这么知道了F8. F8是每年一次Facebook每年一次的开发者大会.每次大会都会release相应的APP,iOS.Android都有.之前都是用Native开发的,但是2 ...

  4. React Native填坑之旅--Navigation篇

    React Native的导航有两种,一种是iOS和Android通用的叫做Navigator,一种是支持iOS的叫做NavigatorIOS.我们这里只讨论通用的Navigator.会了Naviga ...

  5. React Native填坑之旅--ListView篇

    列表显示数据,基本什么应用都是必须.今天就来从浅到深的看看React Native的ListView怎么使用.笔者写作的时候RN版本是0.34. 最简单的 //@flow import React f ...

  6. React Native填坑之旅--动画

    动画是提高用户体验不可缺少的一个元素.恰如其分的动画可以让用户更明确的感知当前的操作是什么. 无疑在使用React Native开发应用的时候也需要动画.这就需要知道RN都给我们提供了那些动画,和每个 ...

  7. React Native填坑之旅--Button篇

    从React过来,发现React Native(以下简称RN)居然没有Button.隔壁的iOS是有UIButton的,隔壁的隔壁的Android里也是有的.没有Button,就没有点击效果啊.这还真 ...

  8. React Native填坑之旅 -- 使用iOS原生视图(高德地图)

    在开发React Native的App的时候,你会遇到很多情况是原生的视图组件已经开发好了的.有的是系统的SDK提供的,有的是第三方试图组件,总之你的APP可以直接使用的原生视图是很多的.React ...

  9. React Native填坑之旅--Stateless组件

    Stateless component也叫无状态组件.有三种方法可以创建无状态组件. 坑 一般一个组件是怎么定义的: 很久以前的方法: const Heading = createClass({ re ...

随机推荐

  1. eclipse查看class文件的源码

    eclipse查看class文件的源码: 1.网上下载jadClipse的jar包和执行文件jad.exe和 net.sf.jadclipse_3.3.0.jar. 2.把上面下载的jar包放在ecp ...

  2. VisualSVN Server导入Repository

    SVN服务器换机器了,原来SVN服务器A的Repository需要原样复制到新的SVN服务器B. 原样复制 Repository ,是为了保持SVN内容不会因换了服务器而导致不同. 那么在新SVN服务 ...

  3. 数据库mysql优化方案

    1.创建索引对于查询占主要的应用来说,索引显得尤为重要.很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致.如果不加索引的话,那么查找任何哪怕只是一条特定的数据 ...

  4. VS工程里的文件都是啥?如何打包? 2015-03-04

    打完补充:以下内容全部是我一家之言,只是愿意分享,内容如有不妥还请见谅. ====================================================== 刚才接收了一份代 ...

  5. MySQL数据库引擎介绍、区别、创建和性能测试的深入分析

    本篇文章是对MySQL数据库引擎介绍.区别.创建和性能测试进行了详细的分析介绍,需要的朋友参考下   数据库引擎介绍 MySQL数据库引擎取决于MySQL在安装的时候是如何被编译的.要添加一个新的引擎 ...

  6. Android中的事件传递机制

    Android源码版本:API Level 19(Android 4.4) Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和双击,另外还包括单指操作和 ...

  7. linux命令(1):ls命令

    ls命令是linux下最常用的命令. ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单. 通过ls 命令不仅可以查看lin ...

  8. html只给自己

    //另外一个 height:400px; weight:400px; border-top-left-radius: 10px; border-top-right-radius: 10px; bord ...

  9. Coursera Machine Learning 作业答案脚本 分享在github上

    Github地址:https://github.com/edward0130/Coursera-ML

  10. Hibernate 注解的用法以及说明(二)

    注解映射必须满足两大条件:Hibernate3.2以上版本和JSEE 5. @Entity 类注释,所有要持久化的类都要有@Entity   public class Org  implements ...