【ReactNative】与iOS组件间的相互调用

React Native目前一般不会用来做整个应用, 因此与原生应用间的通讯就比较重要了.


通讯机制

1.双方均维护一套相同的模块配置表,而这份表是由OC遍历OC模块中带了暴露标记的方法以及JS中配置的模块方法而得到的,该操作是由React Native自动完成的。
2.在JS调用OC方法时,中间件会将该调用做一些格式化操作,在react native中格式化为[ModuleID,MethodID,params(包含CallbackID)],并将其放在等待OC调用的队列中,而CallbackID 则缓存在JS中间件上等待回调。
3.在用户触发了各种事件启动事件,触摸事件,timer事件,系统事件,回调事件时,OC会去调用JS模块配置表中JS模块的方法,此时JS会连同等待OC调用的队列一起传回OC中执行。

原生跳转到RN界面

混合开发最简单的就是原生跳转到RN开发的界面。在自动生成的iOS项目, appDelegate中即直接跳转到RN界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//原生跳转到RN界面
NSURL *jsCodeLocation=[NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]; //URL为Debug Server地址.(如果需要发布, 则需先将js与图片资源打包)
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"Native2RNDemo"
initialProperties:nil
launchOptions:nil];
self.view=rootView;
}

RN中调用原生方法或组件

RCTBridgeModule是定义好的protocol,实现该协议的类,会自动注册到Object-C对应的Bridge中。Object-C Bridge上层负责与Object-C通信,下层负责和JavaScript Bridge通信,而JavaScript Bridge负责和JavaScript通信。
React Native中Object-C相关的命名均以RCT开头。

调用的一般步骤:

1.RCTBridgeModule 负责iOS与JS通讯模块: 引入与实现协议.

1
2
#import "RCTBridgeModule.h"
@interface MyRNTest : NSObject<RCTBridgeModule>

2.RCT_EXPORT_MODULE宏 作用是注册一个Module, Bridge加载后,JavaScript中可调用这个Module.

1
2
3
@implementation MyRNTest
RCT_EXPORT_MODULE(); //写在OC类的实现里即可
@end

3.RCT_EXPORT_METHOD宏 定义被JavaScript调用的方法的宏.

1
2
3
4
RCT_EXPORT_METHOD(myTest:(NSString *) msg ){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"我是OC" message:msg delegate:nil cancelButtonTitle:@"关闭" otherButtonTitles:nil, nil];
[alert show];
}

4.接着就是JS中调用: (ES6的写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, {Component} from 'react'
import {
StyleSheet,
Text,
View,
NativeModules,
TouchableOpacity,
Alert
} from "react-native";
var MyRNTest = NativeModules.MyRNTest; //引入OC中的MyRNTest类
class CalliOS extends Component{
render(){
return (
<View style={mystyle.container}>
<TouchableOpacity onPress={()=>MyRNTest.myTest('hello iOS!')}>
<Text>点击调用iOS中的方法(传递字符串)</Text>
</TouchableOpacity>
</View>
)
}
}

最后在命令行运行:

1
$ react-native run-ios


回调

Native应用处理完之后返回结果再回到JavaScript中进行操作和处理. 这样就需要使用JavaSctipt的回调函数,对结果进行处理。在React Native中Object-c有两种方式的回调:RCTReponseSenderBlock和Promises。
相关官方链接

RCTReponseSenderBlock回调

native中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RCTResponseSenderBlock _alertCallback;
RCT_EXPORT_METHOD(myCallbackTest:(RCTResponseSenderBlock) callback){
_alertCallback = callback;
//callback(@[[NSNull null],@"mydata"]); //可以直接这样用
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"测试回调" message:@"点击按钮回调(RCTResponseSenderBlock方式)" delegate:self cancelButtonTitle:@"关闭" otherButtonTitles:@"确定", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
//1.通过RCTResponseSenderBlock方式处理回调
if(buttonIndex == 0){
_alertCallback(@[@"myerror",]); //为一个数组: 第一个参数为err信息(没错误传入null如下), 后面的为自定义参数
}else{
_alertCallback(@[[NSNull null],@"mydata"]);
}
}

RN中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
render() {
return (
<View style={mystyle.container}>
<TouchableOpacity onPress={()=>RNnativeModule.myCallbackTest(function(err,datas){
if (err) {
Alert.alert('错误',err)
}else{
Alert.alert('确定',datas)
}
})} style={mystyle.button}>
<Text>点击调用iOS中的方法并处理回调(RCTResponseSenderBlock)</Text>
</TouchableOpacity>
</View>
);
}

Promises回调

native中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
RCTPromiseResolveBlock _resolveBlock;
RCTPromiseRejectBlock _rejectBlock;
RCT_REMAP_METHOD(myPromiseTest, resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock) rejecter){
_resolveBlock = resolver;
_rejectBlock = rejecter;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"测试回调" message:@"点击按钮回调(Promise方式)" delegate:self cancelButtonTitle:@"关闭" otherButtonTitles:@"确定", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
//ES6中的Promise方式
if (buttonIndex==0) {
NSError * err=[NSError errorWithDomain:@"Promise Callback Test!" code:0 userInfo:nil];
_rejectBlock(@"0",@"cancel",err); //参数: (NSString *code, NSString *message, NSError *error);
}else{
_resolveBlock(@[@"hello promise callback!"]);
}
}

RN中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//promise
_calliOS_promise(){
RNnativeModule.myPromiseTest().then((datas)=>{
Alert.alert('确定',JSON.stringify(datas)) //回调回来的为json数据, 转为字符串
}).catch((err)=>{
Alert.alert('错误',JSON.stringify(err))
})
}
render() {
return (
<View style={mystyle.container}>
<TouchableOpacity onPress={this._calliOS_promise.bind(this)} style={mystyle.button}>
<Text>点击调用iOS中的方法并处理回调(Promise)</Text>
</TouchableOpacity>
</View>
);
}

原生中调用RN方法(eventDispatcher)

从iOS原生方法发送数据到JavaScript中,那么可以使用eventDispatcher。 参见官方文档翻译链接
需要引入:

1
2
3
4
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
@synthesize bridge = _bridge;

sendAppEventWithName方法访问RN:

1
2
[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
body:@{@"name": @"abcdef"}];

然后在RN中利用生命周期方法componentDidMount()方法中使用addListener()方法进行添加订阅事件。即componentWillUnmount()方法中使用remove()方法取消订阅.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
componentDidMount(){
//订阅
_subscription = NativeAppEventEmitter.addListener('EventReminder',(reminder)=>{
//这里写供native调用的代码.
console.log("RN代码被调用.......");
Alert.alert('接收从OC发过来的事件通知',reminder.name)
})
}
componentWillUnmount(){
//取消订阅
_subscription.remove()
}
render(){
return (
<View style={mystyle.container}>
<TouchableOpacity onPress={()=>nativeRNModule.nativeTest('hello iOS!')} style={mystyle.button}>
<Text>测试iOS调用RN</Text>
</TouchableOpacity>
</View>
)
}

结语

核心还是RCTBridgeModule, 实现objc与javascript之间的通讯.

转载请注明出处,有疑问欢迎留言!