从 swift 或 Objective-c 向 js 发送事件

Posted

技术标签:

【中文标题】从 swift 或 Objective-c 向 js 发送事件【英文标题】:Send event to js from swift or objective-c 【发布时间】:2017-04-06 04:59:56 【问题描述】:

我已经创建了以下类(精简版),这里是对完整文件的引用 https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/ios/CastRemoteNative/NativeMethods.swift

@objc(NativeMethods)
class NativeMethods: RCTEventEmitter 
  @objc(sendEventToJSFromJS)
  func sendEventToJSFromJS 
    self.emitEvent(eventName: "test", body: "bodyTestString")
  
  func emitEvent(eventName: String: body: Any) 
    self.sendEvent(withName: eventName, body: body)
  

当我像下面这样调用emitEvent 方法时,它完美地工作并触发我的javascript代码中的回调侦听器,它是一个改变的sn-p https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/js/Components/ChromecastDevicesModal.js

从 javascript 方面

import 
  NativeModules,
  NativeEventEmitter
 from 'react-native'

//here I bring in the swift class to use inside javascript
var NativeMethods = NativeModules.NativeMethods;

//create an event emitter to use to listen for the native events when they occur
this.eventEmitter = new NativeEventEmitter(NativeMethods);
//listen for the event once it sends
this.subscription = this.eventEmitter.addListener('test', (body) =>  console.log('in test event listener callback', body));

NativeMethods.sendEventToJSFromJS() //call the native method written in swift

我只是在 javascript 中按下按钮时调用 sendEventToJSFromJS 方法

同样,这有效,console.log('in test event listener callback', body) 代码在 javascript 端有效并运行

这不起作用的我的问题:

如果我在定义类后在 swift 文件中执行以下操作,这将不起作用:

var nativeMethodsInstance = nativeMethods()
nativeMethodsInstance.sendEventToJSFromSwift()

为什么?因为抛出如下错误:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'bridge is not set. This is probably because you've explicitly synthesized the bridge in NativeMethods, even though it's inherited from RCTEventEmitter.'

那么,在创建NativeMethods 的instance 时,与不...有什么区别?

更多信息:

当我在 .h 和 .m 文件而不是 .swift 文件中编写这些相同的 sn-ps 代码时,Objective-C 会遇到相同的桥未设置问题

我发现错误消息在本机代码中打印的位置,但它只有变量

_bridge

并且正在检查它是否是nil

此错误来自的文件是:

RCTEventEmitter.h
RCTEventEmitter.c

这是RCTEventEmitter.c的完整sn-p

- (void)sendEventWithName:(NSString *)eventName body:(id)body



  RCTAssert(_bridge != nil, @"bridge is not set. This is probably because you've "
        "explicitly synthesized the bridge in %@, even though it's inherited "
        "from RCTEventEmitter.", [self class]);

  if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) 
    RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
                eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
  
  if (_listenerCount > 0) 
    [_bridge enqueueJSCall:@"RCTDeviceEventEmitter"
                    method:@"emit"
                      args:body ? @[eventName, body] : @[eventName]
                completion:NULL];
   else 
    RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName);
  

这个 _bridge 值在哪里设置以及它是如何设置的,所以我可以知道,在它失败的情况下如何设置它

我还在 RCTEventEmitter.h 中找到了以下内容

@property (nonatomic, weak) RCTBridge *bridge;

在给出的错误中提到桥是在 RCTEventEmitter 中继承的,那么这可能是 bridge 属性的 weak 部分的问题吗?

或者我是否需要改变我如何一起做这一切的策略?

我知道这可能与我没有完全理解有关

@synthesize bridge = _bridge;

部分代码和混入的所有语言都没有多大帮助,哈哈……

这真的很难,所以任何帮助将不胜感激! 非常感谢您的宝贵时间

当项目历史代码代表我上面问题中的代码时,这里是一个完整项目的链接(因为我已经对项目进行了更改):

https://github.com/cotyembry/CastRemoteNative/tree/7e74dbc56f037cc61241f6ece24a94d8c52abb32

【问题讨论】:

【参考方案1】:

我想通了

警告:此解决方案使用已弃用的方法反应本机方法 - 我无法弄清楚如何“正确”从 RCTEventEmitter 继承并发送事件...每次我尝试_bridge 最终会变成 nil

确保将 Swift 桥接到 Objective C(如果您使用 swift 将事件发送到 javascript)

创建导出的 Native 模块的实例(无论它们是用 Swift 还是 Objective C 编写的)

让 React Native 的底层实现来做这件事,对于每个需要发送事件的类,将特定的 Native Class 目标 C 实现代码或 Swift 代码(Native 模块)导出到 React-Native。这允许 javascript 能够监听事件

var publicBridgeHelperInstance = PublicBridgeHelper()  //instantiate the  the objective c class from inside the .swift file to use later when needing to get a reference to the bridge to send an event to javascript written in react native

@objc(DeviceManager)             //export swift module to objective c
class DeviceManager: NSObject 

  @objc(deviceDidComeOnline:)    //expose the function to objective c
  public func deviceDidComeOnline(_ device: GCKDevice) 
    //imagine this deviceDidComeOnline function gets called from something from the Native code (totally independent of javascript) - honestly this could be called from a native button click as well just to test it works...

    //emit an event to a javascript function that is a in react native Component listening for the event like so:

    //1. get a reference to the bridge to send an event through from Native to Javascript in React Native (here is where my custom code comes in to get this to actually work)
    let rnBridge = publicBridgeHelperInstance.getBridge()  //this gets the bridge that is stored in the AppDelegate.m file that was set from the `rootView.bridge` variable (more on this later)

    //(if you want to print the bridge here to make sure it is not `nil` go ahead:
    print("rnBridge = \(rnBridge)")

    //2. actually send the event through the eventDispatcher
    rnBridge?.eventDispatcher().sendAppEvent(withName: "test", body: "testBody data!!!")
  

in AppDelegate.h put(除了文件中已经存在的代码)

#import "YourProjectsBridgingHeaderToMakeThisCodeAvailableInSwift.h"  //replace this with your actual header you created when creating a swift file (google it if you dont know how to bridge swift to objective c)

@interface PublicBridgeHelper: NSObject
  -(RCTBridge*)getBridge;
@end

in AppDelegate.m put(除了文件中已经存在的代码)

#import <React/RCTRootView.h>

RCTBridge *rnBridgeFromRootView;

@implementation PublicBridgeHelper    //this is created to SIMPLY return rnBridgeFromRootView defined above over to my Swift class when actually sending the event to javascript that defines a react native Component
-(RCTBridge*)getBridge 
  NSLog(@"rnBridgeFromRootView = @%@", rnBridgeFromRootView);
  return rnBridgeFromRootView;

重要 - 还要确保将以下代码行添加到 Objective C .h 的桥接头中,以使此 PublicBridgeHelper 定义可用于 .swift 代码中

#import "AppDelegate.h"

最后,

现在向您展示如何设置 AppDelegate.m 中使用的 rnBridgeFromRootView 变量(该变量在将事件发送到 javascript 之前在 .swift 代码中返回并使用)

打开AppDelegate.m并在方法体中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  ... 

在实例化rootView 变量的代码行之后包含以下内容

即在可能看起来像的那一行之后

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"YourProjecNameProbably" initialProperties:nil launchOptions:launchOptions];

添加:

rnBridgeFromRootView = rootView.bridge  //set the bridge to  be exposed and returned later and used by the swift class

现在解释 .swift 文件中的 publicBridgeHelperInstance.getBridge() 部分

publicBridgeHelper 是一个客观 c 类的实例,它允许 swift 类获得对 react native 桥的引用

如果您在阅读本文后仍然无法理解我的回答,我制作了一个视频,您可以在这里观看:

https://www.youtube.com/watch?v=GZj-Vm9cQIg&t=9s

【讨论】:

为什么要将 Objective-c appDelegate 与 swift 代码混合使用? 因为我需要它来将 rootView.bridge(即桥变量)@thibautnoah 暴露给代码中的其他部分 您可能可以通过调整我在 AppDelegate.m 中使用的代码来使用 .swift 版本的应用程序委托,但据我所知,在 Swift 中,使用他们的方法给我带来了该语言的问题用来让我拿到桥(但我已经有一段时间没看了) 在我的应用程序中公开它没有问题,另一方面,子类化 RTCEventEmitter 是痛苦的。 @thibautnoah 你是使用了我的做法还是真的让子类工作来发送事件

以上是关于从 swift 或 Objective-c 向 js 发送事件的主要内容,如果未能解决你的问题,请参考以下文章

从Objective-C向Swift转换学习到的经验

从objective-c++调用swift函数

在 C/C++ 中使用 swift 库?

是否可以以编程方式从 Swift/Objective-C 中的 UIViewController 或 Storyboard 中获取基本 UIViewController 的标识符?

如何从具有隐藏变量名称(参数前的_)或内部+外部(例如 foo(with bar:))的 Objective-C 调用 Swift 函数

如何使用 Swift 从 Objective-C 启动实例类型