RN与原生交互(OC)
有时候App需要访问平安API, 单在ReactNative
中没有相应的模块,或者你需要复用一些Java
代码, 不想用JavaScript
再重新实现一边;有或者你需要实现一些高性能的,多线程代码,譬如图片处理,数据库,或者一些高级扩展等等。
ReactNative
可以与原生平台进行交互
开发iOS原生模块的主要流程
1.编写原生模块的iOS代码
2.暴露接口与数据交互
3.导出ReactNative
原生模块
为了暴露接口以及进行数据交互我们需要借助ReactNative
的React/RCTBridgeModule.h
来操作
#import <React/RCTBridgeModule.h>
@interface AISBridgeModule : NSObject<RCTBridgeModule>
@end
#import "AISBridgeModule.h"
@implementation AISBridgeModule
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();//让RN在主线程回调这些方法
}
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger result=num1+num2;
resolve([NSString stringWithFormat:@"%ld",(long)result]);//回调JS
}
RCT_EXPORT_METHOD(sendMessage:(NSDictionary*)params){//接受RN发过来的消息
[[NSNotificationCenter defaultCenter] postNotificationName:@"sendMessage" object:params];
}
@end
RCT_EXPORT_MODULE
:这个方法不传参数,则采用类名作为导出的模块名,如果传参则采用参数名为模块名
RCT_EXPORT_METHOD
:主要用来声明需要暴露的接口,被标注的方法支持如下数据类型
string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array(NSArray) 包含本列表任意类型
object(NSDictionary) 包含string类型的键与本列表中任意类型的值
function (RCTResponseSenderBlock)
原生模块向JS传递数据我们可以介乎CallBacks
和Promise
CallBacks
原生模块支持一个特殊类型的参数CallBacks
,我们可以通过它来对js进行回调,告诉js调用原生模块方法的结果。
原生
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 success:(RCTResponseSenderBlock)success
failure:(RCTResponseErrorBlock)failure){
NSInteger result=num1+num2;
success(@[[NSString stringWithFormat:@"%ld",(long)result]]);
}
JS调用
const modules = NativeModules.AISBridgeModule
modules.doAdd(parseInt(this.num1), parseInt(this.num2),(error, result) => {
if ( error) {
console.log(error);
} else {
console.log(result);
}
, ()=>{
})
Promises:
如果我们暴露的方法最后一个参数是Promise
,那么JS调用它的时候将会返回一个Promise
原生
RCT_EXPORT_METHOD(doAdd:(NSInteger )num1 num2:(NSInteger )num2 resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger result=num1+num2;
resolve([NSString stringWithFormat:@"%ld",(long)result]);//回调JS
}
JS
const modules = NativeModules.AISBridgeModule
modules.doAdd(parseInt(this.num1), parseInt(this.num2)).then(e => {
this.setState({
result: e
})
})
另外我们也可以用ES2016的async/await
语法来简化我们的代码
async doAdd(){
const modules = NativeModules.AISBridgeModule
var result = await modules.doAdd(parseInt(this.num1), parseInt(this.num2))
}
提示:另外要告诉大家的是无论Callbacks
还是Promise
,我们都只
能调用一次,也就是”you call me once, I can only call you once“
接下来我为大家介绍一种原生模块可以多次向js传递数据的方式
RCTEventEmitter
向JS发送事件
在原生模块中我们可以向js发送多次时间,即使原生模块么有被直接调用。
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface DataToJSPresenter : RCTEventEmitter <RCTBridgeModule>
@end
#import "DataToJSPresenter.h"
@implementation DataToJSPresenter
- (NSArray<NSString *> *)supportedEvents
{
return @[@"testData"];
}
- (instancetype)init {
if (self = [super init]) {//在module初始化的时候注册fireData广播
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fireData:) name:@"fireData" object:nil];
}
return self;
}
- (void)fireData:(NSNotification *)notification{//发送数据给RN
NSString *eventName = notification.object[@"name"];
NSDictionary *params = notification.object[@"params"];
[self sendEventWithName:eventName body:params];
}
@end
事件调度名
-(NSArray<NSString *> *)supportedEvents
发送事件
-(void)sendEventWithName:(NSString *)eventName body:(id)body
eventName
就是我们要发送的事件名,params
是此次事件所带的数据,接下来我们就可以在js模块中监听这个事件了
UNSAFE_componentWillMount() {
// console.log("bridge", JSBridge);
console.log("AISBridgeModule", NativeModules.AISBridgeModule)
// const modules = NativeModules.AISBridgeModule
this.testDataListener = DeviceEventEmitter.addListener('testData', e => {//for Android
this.setState({
data: e.data
})
});
this.dataToJSPresenter = new NativeEventEmitter(NativeModules.DataToJSPresenter);
console.log("dataToJSPresenter", this.dataToJSPresenter)
this.dataToJSPresenter.addListener('testData', (e) => {// for iOS
console.log("testData", e);
this.setState({
data: e.data
})
})
}
不要忘记在组件被卸载的时候移除监听
componentWillUnmount() {
if (this.testDataListener) {
this.testDataListener.remove();
}
if (this.dataToJSPresenter){
// this.dataToJSPresenter.removeListener('testData');
this.dataToJSPresenter.removeAllListeners
}
}
这个是android端的事件发送器代码
private void sendEvent(ReactContext, String eventName, @Nullable WritableMap params){
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(event, params)
}
关于线程:
ReactNative
在一个独立串行的GCD队列中调用原生模块的方法,在我们为ReactNative
开发原生模块的时候,如果有耗时操作,比如:文件的读写,网络操作等,我们需要新开辟一个线程,不然的话,这些耗时操作会阻塞JS线程。通过实现方法- (dispatch_queue_t)methodQueue
,原生可以指定自己在哪个队列中被执行。具体来说如果调用一些必须在主线程中才能使用的API,
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();//让RN在主线程回调这些方法
}
如果操作需要花费很长时间,则应道声明一个用于执行操作的独立队列。举个历史RCTAsyncLocalStorage
模块创建了一个自己的queue
,这样它在做一些较慢的磁盘操作的时候就不会阻塞React
本身的消息队列:
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_CONCURRENT);
}
指定的methodQueue
会被模块里的所有方法功效,如果你的方法只有一个好事较长的(或者由于某种原因必须在不同队列中运行的), 你可以在函数体内用dispatch_async
方法来在另一个队列执行,而不影响其他方法