在ReactNative
开发中, 在JavaScript
语法无法实现的时候会涉及到一些原生开发, 既然是混合开发就会涉及到一些iOS
和ReactNative
之间通讯的问题, 这里就涉及到两种方式:
RN
调用原生的方法, 给原生发送数据
- 原生给
RN
回传数据, 或者给RN
发送通知
- 下面就简单记录下这两种方式的实现
JS调用原生
- 这里要讲的交互场景是
JS
调用原生方法,最后由原生方法将结果回调到JS里面
react-native
是在原生的基础上,将接口调用统一为js
- 也就是说,
react-native
调起原生的能力非常重要
js调用模块
在原生需要创建一个继承自NSObject
的类(模块)
1 2 3 4 5 6 7 8
| #import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface AppEventMoudle : NSObject <RCTBridgeModule>
@end
|
在AppEventMoudle.m
文件件中需要导出改模块, 并将创建的方法导出
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 26 27 28 29 30 31 32 33 34
| #import "AppEventMoudle.h" #import <React/RCTBridge.h>
@implementation AppEventMoudle
RCT_EXPORT_MODULE(AppEventMoudle);
RCT_EXPORT_METHOD(OpenView:(NSDictionary *)params){ dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"params = %@", params); }); }
RCT_EXPORT_METHOD(OpenView:(NSDictionary *)params, callback:(RCTResponseSenderBlock)callback){ dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"params = %@", params); if (callback) { callback(@[params]); } }); }
|
- 桥接到
Javascript
的方法返回值类型必须是void
React Native
的桥接操作是异步的,在queue
里面异步执行,所以如果要返回结果给Javascript
,就必须通过回调或者触发事件来进行
- 这里的回调对应于
iOS
端就是通过block
来回调的
RCTBridge
RCTBridge
可以说是一个封装类,封装了RCTCxxBridge
- 我们先看这个文件提供的一些变量和方法
RCTModuleClasses
: 主要储存的是我们注册的module
, 所有用宏RCT_EXPORT_MODULE()
注册的module
都会存入这个变量.
RCTGetModuleClasses
: 获取RCTModuleClasses
里面所有注册的module
类
RCTBridgeModuleNameForClass
: 从一个类获取这个类的名字
RCTVerifyAllModulesExported
: 验证我们所写的所有遵守RCTBridgeModule
协议的类是否都在我们的管理中
RCTResponseSenderBlock
1
| typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
RCTResponseSenderBlock
是RCTBridgeModule
里面提供的block
- 这个
block
接受一个数组参数, 代表原生方法的返回结果
线程问题
- js代码的执行是在js线程里面,原生模块的执行默认是在一个串行的
queue
里面异步执行的
- 对于原生模块的执行来说,默认一个串行的
queue
是不够的,我们有时候需要指定模块所有任务执行所在的queue
1 2 3 4 5 6 7 8 9
| RCT_EXPORT_METHOD(OpenView:(NSDictionary *)params){ dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"params = %@", params); }); }
|
原生向RN发送监听
- 例如: 项目中的
H5
页面, 通过原生的Webview
实现, 并且监听url
的变化, 并通知js
做相关操作
- 这样我们就要在
url
变化的时候, 给JavaScript
发送监听通知
- 并且不能使用
RCTResponseSenderBlock
进行回调, block
回调只能执行一次, 并不能不断的执行
第一步
- 我们需要创建一个
WebViewController
的控制器, 在该控制器内添加UIWebView
的UI和逻辑的实现
- 在
UIWebViewDelegate
的协议方法中监听webview
的url
的变化, 并发送通知
1 2 3 4 5 6 7 8 9 10
| - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *url= request.URL.absoluteString; if (url && ![url isEqualToString:@""]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"urlChange" object:url]; } [self clickAction:url]; return YES; }
|
第二步
需要在js
调用的方法中接受上述代码中发送的通知, 如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| RCT_EXPORT_METHOD(OpenWebView:(NSDictionary *)params){ dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(urlChange:) name:@"urlChange" object:nil]; WebViewController *webView = [WebViewController new]; webView.params = params; UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:webView]; UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; [rootVC presentViewController:navi animated:YES completion:nil]; }); }
|
第三步
实现监听方法, 并给JavaScript
发送消息通知
1 2 3 4
| - (void)urlChange:(NSNotification *)notification{ [self.bridge.eventDispatcher sendAppEventWithName:@"NativeWebView" body:@{@"url":(NSString *)notification.object}]; }
|
要获取self.bridge
属性, 需要遵循RCTBridgeModule
协议, 并加上如下代码
1
| @synthesize bridge = _bridge;
|
最后不要忘记移除该通知
1 2 3
| - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"urlChange" object:nil]; }
|
第四步
在JavaScript
中接受iOS
原生发送的消息通知
1 2 3 4 5 6 7 8
| this.webViewListener = NativeAppEventEmitter.addListener('NativeWebView', message => { this.handleMessageFromNative(message) })
this.webViewListener && this.webViewListener.remove() this.webViewListener = null
|
原生给js发送事件
- 上面提到的那种方式都是
js
调用iOS
原生代码后, 用iOS
原生在给js
发送事件监听
- 那么如果需要
iOS
原生主动给js
发送监听事件呢, 类似场景: 比如在AppDelegate
中给js
发送事件通知有改如何实现
- 之前遇到过这样一个需求: 需要监听
APP
进入后台和APP
从后台进入前台的事件, 并在JavaScript
中做相关操作
- 不能像之前那种, 定义一个
_bridge
, 并遵循RCTBridgeModule
协议, 就可以使用下面代码发送监听事件了, 加断点可以发现, 下面获取的self.bridge
为nil
1 2 3 4 5
| -(void)applicationDidEnterBackground:(UIApplication *)application { [self.bridge.eventDispatcher sendAppEventWithName:@"NativeWebView" body:@{@"url":(NSString *)notification.object}]; }
|
下面先介绍一个消息监听的实例类
RCTEventEmitter
RCTEventEmitter
是一个基类, 用于发出JavaScript
需要监听的事件, 提供了一下属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @interface RCTEventEmitter : NSObject <RCTBridgeModule>
@property (nonatomic, weak) RCTBridge *bridge;
- (NSArray<NSString *> *)supportedEvents;
- (void)sendEventWithName:(NSString *)name body:(id)body;
- (void)startObserving; - (void)stopObserving;
- (void)addListener:(NSString *)eventName; - (void)removeListeners:(double)count;
@end
|
具体的使用示例, 可继续向下看
第一步
创建一个继承自RCTEventEmitter
的类, 并遵循协议<RCTBridgeModule>
1 2 3 4 5 6 7 8 9 10
| #import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface AppEventManager : RCTEventEmitter <RCTBridgeModule>
@end
NS_ASSUME_NONNULL_END
|
第二步
再具体的iOS
原生代码中发送消息通知
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 26 27 28 29 30 31 32 33
| #import "AppEventManager.h"
@implementation AppEventManager
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents { return @[@"DidEnterBackground", @"DidBecomeActive"]; }
- (void)startObserving { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:@"DidEnterBackground" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:@"DidBecomeActive" object:nil]; }
- (void)stopObserving { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
- (void)applicationDidEnterBackground:(NSNotification *)notification{ [self sendEventWithName:@"DidEnterBackground" body: notification.object]; }
- (void)applicationDidBecomeActive:(NSNotification *)notification{ [self sendEventWithName:@"DidBecomeActive" body: notification.object]; }
|
- 一旦
RCT_EXPORT_MODULE()
声明该类是EXPORT_MODULE
, 那么该类的实例已经创建好了
- 如果你在其他地方创建这个类的实例(
alloc
或 new
), 会导致,ReactNative
不能正确识别该类的实例
第三步
在ReactNative
中引用该模块, 并添加对对应事件的监听即可
先导出iOS
原生定义的模块
1 2
| const appEventMan = new NativeEventEmitter(NativeModules.AppEventManager)
|
使用appEventMan
在对应的地方添加监听即可
1 2 3 4 5 6 7
| this.didEnterBackground = appEventMan.addListener('DidEnterBackground', () => { console.log(`APP开始进入后台---------------`) })
this.didBecomeActive = appEventMan.addListener('DidBecomeActive', () => { console.log(`APP开始从后台进入前台----------`) })
|
但是也不要忘记在对应的地方移除该监听
1 2 3 4 5 6 7
| componentWillUnmount () { this.didEnterBackground && this.didEnterBackground.remove() this.didEnterBackground = null this.didBecomeActive && this.didBecomeActive.remove() this.didBecomeActive = null }
|
至此, 在ReactNative
中JavaScript
和iOS
原生的交互基本就结束了, O(∩_∩)O哈哈~