注册

ReactNative与iOS的交互

本文简要展示RN与iOS原生的交互功能。


1.1 RCTRootView初始化问题


/**
* - Designated initializer -
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;

/**
* - Convenience initializer -
* A bridge will be created internally.
* This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions;


1、当Native APP内只有一处RN的入口时,可以使用initWithBundleURL,否则的话就要使用initWithBridge方法。

2、因为initWithBundleURL会在内部创建一个RCTBridge,当有多个RCTRootView入口时,就会存在多个RCTBridge,容易导致Native端与RN交互时多次响应,出现BUG。



1.2 创建自定义的RNBridgeManager



由于APP内有RN多入口的需求,所以共用一个RCTBridge



RNBridgeManager.h



#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNBridgeManager : RCTBridge
/**
RNBridgeManager单例
*/
+ (instancetype)sharedManager;

@end

NS_ASSUME_NONNULL_END


RNBridgeManager.m


#import "RNBridgeManager.h"

#import <React/RCTBundleURLProvider.h>

//dev模式下:RCTBridge required dispatch_sync to load RCTDevLoadingView Error Fix
#if RCT_DEV
#import <React/RCTDevLoadingView.h>
#endif
/**
自定义类,实现RCTBridgeDelegate
*/
@interface BridgeHandle : NSObject<RCTBridgeDelegate>

@end

@implementation BridgeHandle

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge{
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
}
@end


@implementation RNBridgeManager

+ (instancetype)sharedManager{
static RNBridgeManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[RNBridgeManager alloc] initWithDelegate:[[BridgeHandle alloc] init] launchOptions:nil];
#if RCT_DEV
[manager moduleForClass:[RCTDevLoadingView class]];
#endif
});
return manager;
}


@end


1.3 Native进入RN页面


 RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:[RNBridgeManager sharedManager] moduleName:@"RNTest" initialProperties:nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self.navigationController pushViewController:vc animated:YES];

1.4 RN调用Native方法



  • 创建一个交互的类,实现<RCTBridgeModule>协议;
  • 固定格式:在.m的实现中,首先导出模块名字RCT_EXPORT_MODULE();RCT_EXPORT_MODULE接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Modul的名称;
  • 使用RCT_EXPORT_METHOD导出Native的方法;

1.4.1 比如我们导出Native端的SVProgressHUD提示方法:


RNInterractModule.h


#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNInterractModule : NSObject<RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END


RNInterractModule.m


import "RNInterractModule.h"
#import "Util.h"
#import <SVProgressHUD.h>

@implementation RNInterractModule
////RCT_EXPORT_MODULE接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Modul的名称
RCT_EXPORT_MODULE();

//==============1、提示==============
RCT_EXPORT_METHOD(showInfo:(NSString *) info){
dispatch_sync(dispatch_get_main_queue(), ^{
[SVProgressHUD showInfoWithStatus:info];
});
}
@end


1.4.2 RN端调用导出的showInfo方法:


我们在RN端把Native的方法通过一个共同的utils工具类引入,如下



import { NativeModules } from 'react-native';

//导出Native端的方法
export const { showInfo} = NativeModules.RNInterractModule;

具体的RN页面使用时:


import { showInfo } from "../utils";

//通过Button点击事件触发
<Button
title='1、调用Native提示'
onPress={() => showInfo('我是原生端的提示!')}
/>

调用效果:

image


1.4.3 RN回调Native



RN文档显示,目前iOS端的回调还处于实验阶段



我们提供一个例子来模拟:目前的需求是做面包,RN端能提供面粉,但是不会做,Native端是有做面包的功能;所以我们需要先把面粉,传给Native端,Native加工好面包之后,再通过回调回传给RN端。


Native端提供方法


// 比如调用原生的方法处理图片、视频之类的,处理完成之后再把结果回传到RN页面里去
//TODO(RN文档显示,目前iOS端的回调还处于实验阶段)
RCT_EXPORT_METHOD(patCake:(NSString *)flour successBlock:(RCTResponseSenderBlock)successBlock errorBlock:(RCTResponseErrorBlock)errorBlock){
__weak __typeof(self)weakSelf = self;
dispatch_sync(dispatch_get_main_queue(), ^{
NSString *cake = [weakSelf patCake:flour];
//模拟成功、失败的block判断
if([flour isKindOfClass:[NSString class]]){
successBlock(@[@[cake]]);//此处参数需要放在数组里面
}else{
NSError *error = [NSError errorWithDomain:@"com.RNTest" code:-1 userInfo:@{@"message":@"类型不匹配"}];
errorBlock(error);
}
});
}


//使用RN端传递的参数字符串:"",调用Native端的做面包方法,加工成面包,再回传给RN
- (NSString *)patCake:(NSString *)flour{
NSString * cake = [NSString stringWithFormat:@"使用%@,做好了:🎂🍞🍞🍰🍰🍰",flour];
return cake;
}

RN端调用:


//首先工具类里先引入
export const { showInfo,patCake } = NativeModules.RNInterractModule;


//具体页面使用
<Button
title='4、回调:使用面粉做蛋糕'
onPress={() => patCake('1斤面粉',
(cake) => alert(cake),
(error) => alert('出错了' + error.message))}
/>

调用效果:


image


1.4.4 使用Promise回调


Native端提供方法



RCT_EXPORT_METHOD(callNameTointroduction:(NSString *)name resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock) reject){
__weak __typeof(self)weakSelf = self;
dispatch_sync(dispatch_get_main_queue(), ^{
if ([name isKindOfClass:NSString.class]) {
resolve([weakSelf introduction:name]);
}else{
NSError *error = [NSError errorWithDomain:@"com.RNTest" code:-1 userInfo:@{@"message":@"类型不匹配"}];
reject(@"class_error",@"Needs NSString Class",error);
}
});
}

- (NSString *)introduction:(NSString *)name{
return [NSString stringWithFormat:@"我的名字叫%@,今年18岁,喜欢运动、听歌...",name];
}

RN端调用:


//首先工具类里先引入
export const { showInfo,patCake, callNameTointroduction} = NativeModules.RNInterractModule;

//具体页面使用
<Button
title='5、Promise:点名自我介绍'
onPress={
async () => {
try {
let introduction = await callNameTointroduction('小明');
showInfo(introduction);
} catch (e) {
alert(e.message);
}
}
}
/>


调用效果:

image


1.5 Native端发送通知到RN


Native端继承RCTEventEmitter,实现发送RN通知类:


RNNotificationManager.h



#import <Foundation/Foundation.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNNotificationManager : RCTEventEmitter

+ (instancetype)sharedManager;

@end

NS_ASSUME_NONNULL_END


RNNotificationManager.m



#import "RNNotificationManager.h"

@implementation RNNotificationManager
{
BOOL hasListeners;
}


+ (instancetype)sharedManager{
static RNNotificationManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
manager = [[self alloc] init];
});
return manager;
}

- (instancetype)init{
self = [super init];
if (self) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self];
[center addObserver:self selector:@selector(handleEventNotification:) name:@"kRNNotification_Login" object:nil];
[center addObserver:self selector:@selector(handleEventNotification:) name:@"kRNNotification_Logout" object:nil];
};
return self;
}


RCT_EXPORT_MODULE()

- (NSArray<NSString *> *)supportedEvents{
return @[
@"kRNNotification_Login",
@"kRNNotification_Logout"
];
}
//优化无监听处理的事件
//在添加第一个监听函数时触发
- (void)startObserving{
//setup any upstream listenerse or background tasks as necessary
hasListeners = YES;
NSLog(@"----------->startObserving");
}

//will be called when this mdules's last listener is removed,or on dealloc.
- (void)stopObserving{
//remove upstream listeners,stop unnecessary background tasks.
hasListeners = NO;
NSLog(@"----------->stopObserving");
}

+ (BOOL)requiresMainQueueSetup{
return YES;
}

- (void)handleEventNotification:(NSNotification *)notification{
if (!hasListeners) {
return;
}

NSString *name = notification.name;
NSLog(@"通知名字-------->%@",name);
[self sendEventWithName:name body:notification.userInfo];

}

@end


RN端注册监听:


//utils工具类中导出
export const NativeEmitterModuleIOS = new NativeEventEmitter(NativeModules.RNNotificationManager);


//具体页面使用
import { NativeEmitterModuleIOS } from "../utils";

export default class ActivityScene extends Component {

constructor(props) {
super(props);
this.subscription = null;
this.state = {
loginInfo: '当前未登录',
};
}

updateLoginInfoText = (reminder) => {
this.setState({loginInfo: reminder.message})
};

//添加监听
componentWillMount() {
this.subscription = NativeEmitterModuleIOS.addListener('kRNNotification_Login', this.updateLoginInfoText);

}
//移除监听
componentWillUnmount() {
console.log('ActivityScene--------->', '移除通知');
this.subscription.remove();
}
render() {
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
<Button
title='3、RN Push到Native 发送通知页面'
onPress={() => pushNative(RNEmitter)}
/>
<Text style={{fontSize: 20, color: 'red', textAlign: 'center',marginTop:50}}>{this.state.loginInfo}</Text>
</View>
);
}
}

效果展示:


image


1.6 完整Demo(包含iOS & Android)


RN-NativeTest


作者:BulldogX
链接:https://juejin.cn/post/6844903744061046791
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册