前边介绍了从JS调用iOS原生模块的方法,现在再介绍一下,如何从Native反向通知JS。

一、目录结构

<PROJECT>-Bridging-Header.h // ← here
ReactEvent
├── ReactEvent.h
├── ReactEvent.m
├── ReactEventHandler.swift
├── ReactEventR.h // ← here
├── ReactEventR.m // ← here
└── ReactEventRHandler.swift // ← here

PS: ReactEventR中的R表示是反向的意思。

二、ReactEventR.h

1
2
3
4
5
6
7
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

// ReactEventR: Sending Events to JavaScript
@interface ReactEventR : RCTEventEmitter <RCTBridgeModule>
- (void)emitEvent: (NSString *)event;
@end

三、ReactEventR.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import "ReactEventR.h"
#import <Foundation/Foundation.h>

@implementation ReactEventR
{
  bool hasListeners;
  bool inited;
}

RCT_EXPORT_MODULE();

- (void)startObserving
{
  printf("ReactEventR: startObserving\n");
  hasListeners = YES;
}

- (void)stopObserving
{
  printf("ReactEventR: stopObserving\n");
  hasListeners = NO;
}

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"ReactEventR"];
}

+ (id)allocWithZone:(NSZone *)zone
{
  static RCTBridge *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [super allocWithZone:zone];
  });
  return sharedInstance;
}

- (void)waitForListener
{
  if (inited) return;
  for (int i = 0; i < 10; i++) {
    if (hasListeners) break;
    NSLog(@"ReactEventR: wait for listener: %d", i);
    usleep(200*1000);
  }
  inited = true;
}

- (void)emitEvent:(NSString *)event
{
  [self waitForListener];
  if (hasListeners) {
    NSLog(@"ReactEventR: emit event: %@", event);
    [self sendEventWithName:@"ReactEventR" body:event];
  }
}

@end

PS: 注意到inited变量了吗,此处的实现并不是很优雅,用于确保JS已有监听者,最多等待两秒时间。

四、ReactEventRHandler.swift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import Foundation
class ReactEventRHandler: NSObject {
  static let shared = ReactEventRHandler()
  var handler: ReactEventR?;
  override init() {
    handler = ReactEventR()
  }
  func emit(event: String) {
    NSLog("ReactEventRHandler: event: %@", event)
    handler?.emitEvent(event)
  }
}

五、<PROJECT>-Bridging-Header.h

由于涉及Objc与Swift混合编程,需要写一个桥接头文件

1
2
3
4
5
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "ReactEventR.h" // ← here

然后在Xcode→Project→Target→Build Settings→Swift Compiler - General→Objective-C Bridging Header,填写上你的<PROJECT>-Bridging-Header.h头文件

六、Swift调用示例

1
ReactEventRHandler.shared.emit(event:"SOMETHING") // 单例模式,可直接使用

七、JS接收使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import {NativeModules,NativeEventEmitter} from 'react-native';
const { ReactEventR } = NativeModules; // ← here
class Home extends React.Component {
    componentDidMount() {
      const ReactEventREmitter = new NativeEventEmitter(ReactEventR); // ← here
      this.rer_subscription = ReactEventREmitter.addListener( // ← here
        'ReactEventR',
        value => {
          // TODO 更新状态
          console.warn("ReactEventR: come value:", value)
        }
      )
      // ...
    }
    componentWillUnmount(){
      // ...
      this.rer_subscription && this.rer_subscription.remove(); // ← here
    }
    // ...
}

参考链接

  1. iOS开发-与ReactNative交互时bridge is not set
  2. kevinejohn/react-native-keyevent#KeyEventModule.java
  3. Sending Events to JavaScript#坑
  4. Sending events to JavaScript from your native module in React Native