无法使用 iOS 从主线程方法发送事件并对本机做出反应 [英] Can't send an event from main thread method with iOS and react native
问题描述
我有以下课程:
#import "EventHandler.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import <pthread.h>
@implementation EventHandler
RCT_EXPORT_MODULE();
@synthesize bridge = _bridge;
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
mach_port_t machTID = pthread_mach_thread_np(pthread_self());
NSLog(@"Pretending to create an event %@ at %@, current thread: %x", name, location, machTID);
[self updateLocationEvent];
}
- (void)updateLocationEvent
{
NSString *eventName = @"name!!!";
mach_port_t machTID = pthread_mach_thread_np(pthread_self());
NSLog(@"about to submit event, current thread: %x",machTID);
[self.bridge.eventDispatcher sendAppEventWithName:@"UpdateLocation"
body:@{@"name": eventName}];
}
@end
我的问题:我试图从主线程方法
My problem: I'm trying with no success to run updateLocationEvent
method from main thread method
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
其中(对于那些不熟悉它的人)是一种在发生重大变化时跟踪位置的方法.从 didUpdateLocations
调用时,一旦进入 updateLocationEvent
方法,属性 self.bridge
就是 nil
!如果您查看其他方法 RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
我也从那里调用 updateLocationEvent
并发出事件就好了.实际上,我只是创建了一个方法来测试事件是否被触发,并且确实发生了.处理 didUpdateLocations
的类有一个 EventHandler
属性,它的初始化方式与我在objective-c 中看到的人们所做的相同:
Which (for those who are not familiar with it) is a method that tracks location whenever there's a significant change. Once inside the updateLocationEvent
method while calling from didUpdateLocations
the property self.bridge
is nil
! If you take a look at the other method RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
I also call updateLocationEvent
from there and it emits the event just fine. Actually I just created the method to test if the event was getting fired at all and it did. The class that handles didUpdateLocations
has an EventHandler
property which is initialized the same way I've seen people do in objective-c:
if (_eventHandler == nil) {
_eventHandler = [[EventHandler alloc] init];
}
[_eventHandler updateLocationEvent];
也许我做错了?我对 obj-c 非常陌生,而且相对较新,但我还没有找到一个示例,我可以在其中从本机方法执行事件发射方法,因此我们将不胜感激.
Perhaps I'm doing this wrong? I'm very new to obj-c and relatively new to react but I just haven't found an example where I can execute an event emitting method from a native method so any help would be greatly appreciated.
推荐答案
问题不在于您从主线程调用,也不在于您的模块代码有任何问题.它不工作的原因是模块的桥属性尚未设置(为零).
The issue is not that you are calling from the main thread, nor is there anything wrong with your module code. The reason it's not working is that the bridge property of the module hasn't been set (it's nil).
真正的问题是您正在尝试使用 [[EventHandler alloc] init]
创建自己的模块实例,以便您可以调用 updateLocationEvent
方法,但这不是 React 中模块的使用方式.
The real problem is that you are trying to create your own instance of the module using [[EventHandler alloc] init]
so that you can call the updateLocationEvent
method, but that isn't how modules are used in React.
您永远不应该自己初始化 RCTBridgeModules(除非您通过 moduleProvider 块或 extraModules
委托方法将它们传递到桥中,但这可能与此处无关).模块在您创建时由桥自动实例化(通常在您的应用程序委托中),并且它们与桥的生命周期相关联.每个桥接实例在实例化模块时都会设置模块的 bridge
属性,因此如果您自己创建模块的新实例,它们的桥接属性将永远不会被设置.
You should never init RCTBridgeModules yourself (unless you are passing them into the bridge via the moduleProvider block or extraModules
delegate method, but that's probably not relevant here). Modules are instantiated automatically by the bridge when you create it (typically in your app delegate), and they are tied to the bridge's lifecycle. Each bridge instance sets the bridge
property of the modules when it instantiates them, so if you create new instances of the module yourself, their bridge property will never be set.
使用[bridge moduleForClass:]
方法从外部访问桥接模块实例是可能的(虽然很复杂),但我们使它变得困难的原因是因为这通常是一个坏主意.您不应该尝试在外部调用模块上的方法,因为您不知道桥接器是否已经完成加载 JS 代码,或者它是否在重新加载后被拆除.
It is possible (though complicated) to access bridge module instances externally by using the [bridge moduleForClass:]
method, but the reason we've made it difficult is because it's generally a bad idea. You shouldn't try to call methods on your module externally because you don't know if the bridge has finished loading the JS code yet, or if it's in the process of being torn down after a reload.
将事件发送到 JS 的正确方法几乎总是从您的模块代码内部进行,以便桥接器可以管理您的代码的生命周期,并确保它只在一切设置好后才被调用正确启动.
The correct way to send events to JS is almost always do it from inside your module code, so that the bridge can manage the lifecycle of your code and make sure it's only called once everything is set up correctly.
Elliot 在上面说 RCTLocationObserver
已经提供了这个功能是正确的,所以我不确定你为什么需要复制它,但假设你有你的理由,理想情况下(如 Elliot 建议的那样)在上面的选项 2 中),您的 EventHandler
类将被设置为 CLLocationManager
的委托,并将实现 - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
内部.像这样:
Elliot is correct above when he says that RCTLocationObserver
already provides this functionality, so I'm not exactly sure why you need to replicate it, but assuming you have your reasons, ideally (as Elliot suggests in his option 2 above), your EventHandler
class would be set up as the delegate for CLLocationManager
, and would implement the - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
internally. Something like this:
@interface EventHandler () <CLLocationManagerDelegate>
@end
@implementation EventHandler
{
CLLocationManager *_locationManager;
}
RCT_EXPORT_MODULE();
@synthesize bridge = _bridge;
- (instancetype)init
{
if ((self = [super init])) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[_locationManager startUpdatingLocation];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
NSString *eventName = @"name!!!";
[self.bridge.eventDispatcher sendAppEventWithName:@"UpdateLocation"
body:@{@"name": eventName}];
}
@end
这基本上是我们自己的 RCTLocationObserver
的工作方式,但请注意,在此实现中,将提示用户立即启用定位服务,而您可能更喜欢导出 startUpdatingLocation
> 方法添加到 JS,以便它可以在您的应用程序中的适当时间调用.
This is basically how our own RCTLocationObserver
works, although note that in this implementation the user will be prompted to enable location services immediately, whereas you may prefer to export the startUpdatingLocation
method to JS so it can be called at the appropriate time in your application.
我不确定 Elliot 的选项 no 到底是什么意思.3 使用单例,因为这会遇到同样的问题,即桥不知道该实例,因此永远不会为您设置桥属性.我还建议一般不要使用模块的单例实例,因为它们会在桥重新加载 (cmd-R) 后保持其状态,这可能会导致奇怪的错误或意外行为.每次重新加载时,最好让桥拆除并重新创建所有模块,这是其默认行为.
I'm not sure exactly what Elliot meant with his option no. 3 of using a singleton, as this would suffer from the same problem that the bridge would be unaware of that instance, and so would never set the bridge property for you. I'd also advise against singleton instances of modules in general because they would retain their state after a bridge reload (cmd-R), which may lead to odd bugs or unexpected behaviour. It's better to let the bridge tear down and recreate all the modules every time it is reloaded, which is its default behaviour.
这篇关于无法使用 iOS 从主线程方法发送事件并对本机做出反应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!