React Native 进阶原生模块封装基础篇详解 - 适配 iOS 开发

2,881 阅读8分钟

尊重版权,未经授权不得转载

本文来自:江清清的技术专栏

(一)前言

之前我们已经讲解过封装Android的原生模块(具体地址:www.lcode.org/?p=1611) ,有Android部分封装,当然也少不了iOS平台原生模块封装啦,今天我们就一起来看一下。

刚创建的React Native技术交流5群(386216878),欢迎各位大牛,React Native技术爱好者加入交流!同时博客右侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!

本文章实例项目地址github.com/jiangqqlmj/…

有时候我们的应用需要进行访问原生平台系统的API接口,但是React Native可能还没有封装相应功能组件。还有可能我们需要去复用一些原生Objective-C,Swift或者C++代码而不是让JavaScript重新去实现一遍。或者我们可能需要些一些更加高级的功能代码,所线程相关的。例如:图片处理,数据库以及一些高级功能扩展之类的。

React Native平台的开发其实本身也是可以让你写纯原生代码并且还可以让你访问原生平台的功能。这是一个比较高级的功能不过官方还是不推荐你在平时开发中使用这样的开发形式。但是如果你具备这样的开发能力,也是还是不错的。特别当在React Native暂时未提供部分原生功能或者模块,那么你可以使用这样的方法进行封装扩展。今天我们就来看一下原生组件的封装扩展方法。

本文章就是演示如何封装原生模块,不过前提假设你已经知道熟悉Objective-C或者Swift语言以及一些核心库(例如:Foundation,UIKit)。

本次我们就拿iOS日历模块进行演示。

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

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

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

// CalendarManager.h
#import "RCTBridgeModule.h"

@interface CalendarManager : NSObject 
@end

为了实现RCTBridgeModule协议,你的CalendarManger类.m文件需要实现RCT_EXPORT_MODULE()宏,该宏有一个可选的参数可以设置指定在JavaScript端进行访问该模块的名称(更多细节可以往后面继续看)。如果你这边没有设置该名称,那么JavaScript会默认去使用Objecttive-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 (
      
        {this.props.text}
      
    );
  }
}
class ModulesDemo extends Component {
  render() {
    return (
      
        
            封装iOS原生模块实例
        
        CalendarManager.addEvent('生日聚会', '江苏南通 中天路')}
        />
      
    );
  }
}
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);

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

查看图片

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

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

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

(三)参数类型

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

3.1.string(NSString )

3.2.number(NSInteger,float,double,CGFloat,NSNumber)

3.3.boolean(BOOL,NSNumber)

3.4.array(NSArray)  任何类型的集合

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

3.6.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
//
//  Created by 江清清 on 16/5/22.
//  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 (
      
        {this.props.text}
      
    );
  }
}
class ModulesDemo extends Component {
  render() {
    return (
      
        
            封装iOS原生模块实例
        
        CalendarManager.addEvent('生日聚会', '江苏南通 中天路')}
        />
        CalendarManager.addEventMoreDate('生日聚会', '江苏南通 中天路',1463987752)}
        />
        CalendarManager.addEventMoreDetails('生日聚会', {
              location:'江苏 南通市 中天路',
              time:1463987752,
              description:'请一定准时来哦~'
            })}
        />
      
    );
  }
}
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);

运行效果如下:

查看图片

(五)最后总结

今天我们主要学习了一下React Native For iOS平台封装原生模块,大家有问题可以加一下群React Native技术交流5群(386216878).或者底下进行回复一下。 本文章实例项目地址github.com/jiangqqlmj/…

尊重原创,未经授权不得转载:From Sky丶清(www.lcode.org/) 侵权必究!

关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

查看图片