React Native 与iOS的通信

Posted 一步一迹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native 与iOS的通信相关的知识,希望对你有一定的参考价值。

RN可以很好的与原生进行交互,我们首先看看效果吧:
bridge

首先我们来看看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,源码下载

以上是关于React Native 与iOS的通信的主要内容,如果未能解决你的问题,请参考以下文章

react native js 与 native 的通信与交互方式

REACT NATIVE 系列教程之十二REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信

React Native深入理解Native与RN通信原理

React Native深入理解Native与RN通信原理

React Native深入理解Native与RN通信原理

React Native深入理解Native与RN通信原理