RN可以很好的与原生进行交互,我们首先看看效果吧:
首先我们来看看React Native 怎样调用iOS的代码并且带有简单的参数:
在iOS工程里面我们新建一个类iOSExport,iOSExport将会实现RCTBridgeModule协议。
首先我们要在iOSExport类的实现中添加这句宏定义:RCT_EXPORT_MODULE()
RCT_EXPORT_MODULE()如果你不传入参数,那么你在iOS中导出的模块名就是类名,你也可以插入参数作为自定义模块名。
@implementation iOSExport
//定义导出的模块名
RCT_EXPORT_MODULE()
@end
后面我们就可以实现协议的代理方法了,协议方法的实现需要在RCT_EXPORT_METHOD,这个宏里面。
我们先写一个有两个参数的方法给js调用:
@implementation iOSExport
//定义导出的模块名
RCT_EXPORT_MODULE()
//定义导出的方法名
RCT_EXPORT_METHOD(rnToiOS:(NSString *)name :(NSInteger)age) {
NSString *st = [NSString stringWithFormat:@"name:%@,age:%ld",name,age];
NSLog(@"test:%@",st);
[self alter:st];
}
@end
这样OC端的工作就OK了,下面我们继续看看js端怎么调用:
首先我们要在js文件里面 import NativeModules
然后在我们需要使用的时候获取导出的模块,我们再用模块调用iOS的导出的函数名就可以了,看代码:
//创建一个可以点击的按钮,点击按钮后调用iOS的rnToiOS方法
<TouchableHighlight
style={[styles.highLight,{marginTop:50}]}
underlayColor=‘#deb887‘
activeOpacity={0.8}
onPress={() => this._nameAndAge()}
>
<Text>简单数据传递</Text>
</TouchableHighlight>
_nameAndAge() { //多参数的传递
var iOSExport = NativeModules.iOSExport //获取到模块
iOSExport.rnToiOS(‘帝君‘,200) //直接调用函数
this.setState({
text:‘rnToiOS‘
})
}
下面我们再看如何在js端调用iOS的含有字典参数和回调函数的方法。iOS提供给js的回调函数是使用block实现的,看下回调函数的说明:
/**
* The type of a block that is capable of sending a response to a bridged
* operation. Use this for returning callback methods to JS.
*/
typedef void (^RCTResponseSenderBlock)(NSArray *response);
下面我们就可以用回调函数做参数,写一个我们需要的方法:
RCT_EXPORT_METHOD(rnToiOSwithDic:(NSDictionary*)dic andCallback:(RCTResponseSenderBlock)callback) {
NSMutableString *st = [NSMutableString string];
for (NSObject *key in dic.allKeys) {
NSString *string = [NSString stringWithFormat:@"%@:%@;",key,[dic objectForKey:key]];
[st appendString:string];
}
callback(@[@"error",st]);
[self alter:st];
}
最终我们的回调函数给js的是一个数组,一般这个数组的第一个元素表示的都是错误。
看下如何在js端调用这个方法:
//为了测试方便我们先写个按钮
<TouchableHighlight
style={styles.highLight}
underlayColor=‘coral‘
activeOpacity={0.8}
onPress={() => this._dic()}
>
<Text>字典的传递和回调</Text>
</TouchableHighlight>
//字典的传递和返回值
_dic() {
var iOSExport = NativeModules.iOSExport //获取导出的模块
iOSExport.rnToiOSwithDic({ //调用iOS的方法,第一个参数是字典
‘姓名‘:‘幽冥‘,
‘年龄‘:20,
‘法力‘:‘200‘
},(error,strings) =>{ //第二个参数是函数,做为回调函数给iOS将由iOS调用
this.setState({
text:strings
})
})
this.setState({
text:‘rnToiOSwithDic‘
})
}
上面对于回调函数的处理稍显麻烦,我们再看使用promise实现的回调函数:
我们先看下iOS里面的两个block:
/**
* Block that bridge modules use to resolve the JS promise waiting for a result.
* Nil results are supported and are converted to JS‘s undefined value.
*/
typedef void (^RCTPromiseResolveBlock)(id result);
/**
* Block that bridge modules use to reject the JS promise waiting for a result.
* The error may be nil but it is preferable to pass an NSError object for more
* precise error messages.
*/
typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);
这两个block其实就是实现promise回调的关键,一个是成功的回调一个是失败的回调,看下iOS端的实现:
RCT_EXPORT_METHOD(rnToiOSAge:(NSInteger)age resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
if (age > 23) {
resolve(@[@"句芒"]);
}else {
reject(@"101",@"年龄错误",[NSError errorWithDomain:@"错误" code:1 userInfo:nil]);
}
}
在js里面我要调用这个方法其实只要传一个参数age就可以了,后面两个参数可能会转换为promis作为返回值处理,看下js端是怎么调用的:
//作为测试的按钮
<TouchableHighlight
style={styles.highLight}
underlayColor=‘#5f9ea0‘
activeOpacity={0.8}
onPress={() => this._promise(30)}
>
<Text>Promise回调</Text>
</TouchableHighlight>
//按钮的回调事件,将在这里调用iOS的方法
async _promise(age) { //Promise回调,异步执行
try{
var iOSExport = NativeModules.iOSExport
var resolve = await iOSExport.rnToiOSAge(age) //执行iOS函数并且等待结果
this.setState({
text:resolve
})
}catch(e) {
console.error(e);
}
}
这里要说下,因为我们是采用promsie返回值的方式处理回调的,所以我们不知道何时将返回结果,所以按钮的点击函数_promise(age)前面要加个async关键字标识为异步函数,我们在函数里面可以使用try{}catch(){}来捕获异常,因为我们明确的知道如果age小于24就将报错,所以我们在这里一定要添加异常处理。注意了在执行iOS函数的时候也要加await关键字的,等待获取到返回值后再执行下面的操作。
在iOS里面也可以很方便地给js注入常量,采用如下方法可以方便的提供常量:
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.ModuleName.X. It is only called once for the lifetime of the
* bridge, so it is not suitable for returning dynamic values, but may be used
* for long-lived values such as session keys, that are regenerated only as
* part of a reload of the entire React application.
*/
- (NSDictionary<NSString *, id> *)constantsToExport;
这里要注意了,这函数只会在桥接的过程中执行一次,所以不太适合变量的传递,iOS里面的实现:
//为js提供静态数据
- (NSDictionary<NSString *,id> *)constantsToExport {
return @{@"name":@"闲",@"age":@"22"};
}
js如何调用的:
<TouchableHighlight
style={styles.highLight}
underlayColor=‘#5f9ea0‘
activeOpacity={0.8}
onPress={() => this._getConst()}
>
<Text>获取iOS常量</Text>
</TouchableHighlight>
_getConst() {
var iOSExport = NativeModules.iOSExport //获取模块
this.setState({
text:iOSExport.name+‘,‘+iOSExport.age //获取常量
})
}
在上面这些函数里面如果我想统一指定他们在什么线程里执行的,只要实现这函数就可以了:
//告诉程序这个模块的代码在哪个线程执行
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue(); //返回一个指定的线程
}
前面说了那么多都是怎么在js里面调用iOS的方法的,那么如何让iOS主动发送消息给js呢。
首先我们更改一下iOSExport这个类,让它继承自RCTEventEmitter,并且删除协议,因为RCTEventEmitter这个类也会实现RCTBridgeModule协议的:
//继承自RCTEventEmitter的OC类将有资格给js发送消息
@interface iOSExport :RCTEventEmitter
@end
然后在iOSExport的实现里面我们还要重写一个方法,用来指定这模块将会发送哪些消息给js :
- (NSArray<NSString *> *)supportedEvents {
return @[@"sendName"]; //这里返回的将是你要发送的消息名的数组。
}
然后我们就可以直接发送消息了:
- (void)alter:(NSString *)st {
UIAlertController *alter = [UIAlertController alertControllerWithTitle:@"测试" message:st preferredStyle:UIAlertControllerStyleAlert];
[alter addAction:[UIAlertAction actionWithTitle:@"了解" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
//iOS发送通知给js
[self sendEventWithName:@"sendName" body:@{@"name":@"江山",@"age":@"5000"}];
}]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alter animated:YES completion:nil];
}
我们使用- (void)sendEventWithName:(NSString *)eventName body:(id)body
发送消息,eventName将是消息的名字,body可以传个字典作为消息体。
那么如何在js里面接受消息呢:
首先要import NativeEventEmitter,然后我们注册监听事件:
componentWillMount() {
//开始监听
var iOSExport = NativeModules.iOSExport
var emitter = new NativeEventEmitter(iOSExport) //用获取的模块创建监听器
this.subScription = emitter.addListener("sendName",(body) => this._getNotice(body)) //监听指定的事件,通过sendName这事件名来识别事件,(body) => this._getNotice(body)这是监听到事件后的处理方法,body 是iOS传过来的消息体
}
_getNotice (body) {
this.setState({
notice:body.name+‘,‘+body.age
})
}
最后我们要注销监听:
componentWillUnmount() {
//删除监听
this.subScription.remove()
}
以上源码都已经上传gitub,源码下载