React Native 与原生模块数据通信(一)(iOS)

96
manofit
2018.05.24 14:50* 字数 1159

(一)iOS日历模块封装演示

下面开始演示如何封装一个iOS日历原生模块,让JavaScript可以进行访问到iOS平台日历的功能。

在React Native中,原生模块就是一个Objective-C类,并且应该实现了RCTBridgeModule协议。其中RCT是ReaCT的缩写。我们看一下CalendarManger.h官方的代码:

// CalendarManager.h
#import "RCTBridgeModule.h"
@interface CalendarManager : NSObject <RCTBridgeModule>

@end

为了实现RCTBridgeModule协议,你的CalendarManger类.m文件需要实现RCT_EXPORT_MODULE()宏,该宏有一个可选的参数可以设置指定在JavaScript端进行访问该模块的名称(更多细节可以往后面继续看)。如果你这边没有设置该名称,那么JavaScript会默认去使用Objective-C类名称。

到这一步CalendarManager.m文件的配置如下:

#import "CalendarManager.h"

@implementation CalendarManager
RCT_EXPORT_MODULE()
@end

下面就是注入提供给JavaScript调用的方法,该方法必须要使用RCT_EXPORT_METHOD()宏来声明,不然这些方法JavaScript前端是无法进行调用的。看下面官方的代码:

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

完成以上的步骤,我们就可以从JavaScript端进行访问这个方法了。具体调用方式如下:

import { NativeModules } from 'react-native';
var CalendarManager = NativeModules.CalendarManager;
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');

来最终JavaScript中的代码如下:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
import React,{
  AppRegistry,
  Component,
  StyleSheet,
  Text,
  View,
  TouchableHighlight,
} from 'react-native';
///进行导入NativeModules中的CalendarManger模块
import { NativeModules } from 'react-native';
var CalendarManager = NativeModules.CalendarManager;
class CustomButton extends React.Component {
  render() {
    return (
      <TouchableHighlight
        style={styles.button}
        underlayColor="#a5a5a5"
        onPress={this.props.onPress}>
        <Text style={styles.buttonText}>{this.props.text}</Text>
      </TouchableHighlight>
    );
  }
}
class ModulesDemo extends Component {
  render() {
    return (
      <View style={{marginTop:20}}>
        <Text style={styles.welcome}>
            封装iOS原生模块实例
        </Text>
        <CustomButton text="点击调用原生模块addEvent方法"
            onPress={()=>CalendarManager.addEvent('生日聚会', '江苏南通 中天路')}
        />
      </View>
    );
  }
}
const styles = StyleSheet.create({
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  button: {
    margin:5,
    backgroundColor: 'white',
    padding: 10,
    borderWidth:1,
    borderColor: '#cdcdcd',
  },
});
AppRegistry.registerComponent('ModulesDemo', () => ModulesDemo);

点击按钮,会去调用原生模块方法,并且传入数据打印在控制台,效果截图如下:


22.jpg

[特别注意].JavaScript调用方法名称

封装的原生模块方法给JavaScript进行调用,通过该方法的名字进行调用。有时候可能出现同名冲突的问题,在React Native中,还好给我们定义了RCT_REMAP_METHOD()宏来进行重置JavaScript调用的方法名称。该宏在多个原生模块中可能封装了同样的方法名称非常有用,可以解决同名冲突问题。

CalendarManager模块在Objective-C中必须要使用[CalendarManager new]进行初始化,并且该用于桥接调用的方法的返回值始终为void。React Native桥接的方法是异步的,所以传递一个返回值给JavaScript只能通过回调或者发送事件解决(具体我们会在后面的文章中讲解)。

(二)参数类型

RCT_EXPORT_METHOD支持所有的标准的JSON对象类型,如下:

  • string(NSString )

  • number(NSInteger,float,double,CGFloat,NSNumber)

  • boolean(BOOL,NSNumber)

  • array(NSArray) 任何类型的集合

  • map(NSDictionary) 字典类型,包括任何类型键值对的集合

  • function(RCTResponseSenderBlock) block回调对象方法

除此之外,支持RCTConvert类支持的类型也都支持(大家可以查看RCTConvert来了解详情)。RCTConvert还提供了一些方法可以把输入接收的JSON数据转换成原生的Objective-C类型或者类。

例如在我们本个CalendarManger例子中,我们需要通过桥接把JavaScript中的事件的时间发送给原生封装的方法,但是我是不能直接通过桥接进行传递JavaScript的时间对象的,所以我们这边需要把时间转换成一个字符串或者数字进行传递。下面我们来看一下实例的方法:

//对外提供调用方法,为了演示事件时间格式化
RCT_EXPORT_METHOD(addEventMore:(NSString *)name location:(NSString *)location data:(NSNumber *)secondsSinceUnixEpoch){
   NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch];
}

或者像以下的写法:

RCT_EXPORT_METHOD(addEventMoreTwo:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString)
{
  NSDate *date = [RCTConvert NSDate:ISO8601DateString];
}

其实我们还可以依靠类型自动转换的原理,跳过手动类型转换的步骤,直接像如下这样写,也是可以的

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date)
{
  // Date is ready to use!
}

接下来我们就可以在JavaScript文件中使用如下两种方法的一种进行调用了

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); //把日期以unix时间戳形式传递

或者:

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // // 把日期以ISO-8601的字符串形式传递

以上两种值传递的方法都可以正确的转换成原生平台的NSDate类型,如果传值错误的话,例如:传递Array类型值,会出现红屏幕的错误。

随着业务的发展CalendarManager.addEvent的方法会变得越来越复杂,同样参数也会越来越多。很多时候这些参数可能是可选的,在这种情况下面我们需要修改我们的API,可以接受一个事件属性的字典类型,如下代码:

#import "RCTConvert.h"
 
RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details)
{
  NSString *location = [RCTConvert NSString:details[@"location"]];
  NSDate *time = [RCTConvert NSDate:details[@"time"]];
  ...
}

接下来我们在JavaScript中如下进行调用即可:

CalendarManager.addEvent('Birthday Party', {
  location: '4 Privet Drive, Surrey',
  time: date.getTime(),
  description: '...'
})

(三)实战实例

上面讲了那么多,基本都是概念和流程,现在我们直接演示一个实例让大家看一下,首先看一下封装的原生代码:

//
//  CalendarManager.m
//  ModulesDemo
//
//  Copyright © 2016年 Facebook. All rights reserved.
//
 
#import "CalendarManager.h"
#import "RCTConvert.h"
@implementation CalendarManager
//默认名称
RCT_EXPORT_MODULE()
//对外提供调用方法
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location){
  NSLog(@"Pretending to create an event %@ at %@", name, location);
}
//对外提供调用方法,为了演示事件时间格式化 secondsSinceUnixEpoch
RCT_EXPORT_METHOD(addEventMore:(NSString *)name location:(NSString *)location data:(NSNumber*)secondsSinceUnixEpoch){
   NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch];
}
//对外提供调用方法,为了演示事件时间格式化 ISO8601DateString
RCT_EXPORT_METHOD(addEventMoreTwo:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString)
{
  NSDate *date = [RCTConvert NSDate:ISO8601DateString];
}
//对外提供调用方法,为了演示事件时间格式化 自动类型转换
RCT_EXPORT_METHOD(addEventMoreDate:(NSString *)name location:(NSString *)location date:(NSDate *)date)
{
   NSDateFormatter *formatter = [[NSDateFormatter alloc] init] ;
  [formatter setDateFormat:@"yyyy-MM-dd"];
   NSLog(@"获取的事件信息:%@,地点:%@,时间:%@",name,location,[formatter stringFromDate:date]);
}
 
//对外提供调用方法,为了演示事件时间格式化 传入属性字段
RCT_EXPORT_METHOD(addEventMoreDetails:(NSString *)name details:(NSDictionary *) dictionary)
{
  NSString *location = [RCTConvert NSString:dictionary[@"location"]];
  NSDate *time = [RCTConvert NSDate:dictionary[@"time"]];
  NSString *description=[RCTConvert NSString:dictionary[@"description"]];
  NSLog(@"获取的事件信息:%@,地点:%@,时间:%@,备注信息:%@",name,location,time,description);
 
}
@end

前端JS调用方法如下:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
import React,{
  AppRegistry,
  Component,
  StyleSheet,
  Text,
  View,
  TouchableHighlight,
} from 'react-native';
///进行导入NativeModules中的CalendarManger模块
import { NativeModules } from 'react-native';
var CalendarManager = NativeModules.CalendarManager;
class CustomButton extends React.Component {
  render() {
    return (
      <TouchableHighlight
        style={styles.button}
        underlayColor="#a5a5a5"
        onPress={this.props.onPress}>
        <Text style={styles.buttonText}>{this.props.text}</Text>
      </TouchableHighlight>
    );
  }
}
class ModulesDemo extends Component {
  render() {
    return (
      <View style={{marginTop:20}}>
        <Text style={styles.welcome}>
            封装iOS原生模块实例
        </Text>
        <CustomButton text="点击调用原生模块addEvent方法"
            onPress={()=>CalendarManager.addEvent('生日聚会', '江苏南通 中天路')}
        />
        <CustomButton text="点击调用原生模块addEvent方法"
            onPress={()=>CalendarManager.addEventMoreDate('生日聚会', '江苏南通 中天路',1463987752)}
        />
        <CustomButton text="调用原生模块addEvent方法-传入字段格式"
            onPress={()=>CalendarManager.addEventMoreDetails('生日聚会', {
              location:'江苏 南通市 中天路',
              time:1463987752,
              description:'请一定准时来哦~'
            })}
        />
      </View>
    );
  }
}
const styles = StyleSheet.create({
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  button: {
    margin:5,
    backgroundColor: 'white',
    padding: 10,
    borderWidth:1,
    borderColor: '#cdcdcd',
  },
});
AppRegistry.registerComponent('ModulesDemo', () => ModulesDemo);

打个小广告:这是我正在撸的RN项目,目前完成的有网易新闻、美团>>>,还会继续添加一些功能,希望支持~~~

ReactNative
Web note ad 1