react-native/React/Modules/RCTEventEmitter.m
Daniele Conti 08c404d293 Eagerly change the listeners count
Summary:
While working with `RCTEventEmitter` I noticed that if an event is emitted before `_listenerCount` is updated, it will not go through because the listeners count hasn't been updated. Moving the count update before the invokation of `startObserving` and `stopObserving` fixes the issue. Same way if you remove the last listener and an event is fired before the count is updated (while it shouldn't be fired).

**Test plan (required)**

An easy test to demonstrate it is to implement `startObserving` to synchronously fire an event. Without the change, a warning is thrown, with the change, the event is fired. Not very strong on Obj-C here and I didn't know how to mock out the native stuff. Would be glad to write a failing unit test tho :)
Closes https://github.com/facebook/react-native/pull/11907

Differential Revision: D4738965

Pulled By: javache

fbshipit-source-id: cf175051be5b9c5de761d3dcd290560e1639b05e
2017-03-20 12:49:00 -07:00

99 lines
2.5 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTEventEmitter.h"
#import "RCTAssert.h"
#import "RCTUtils.h"
#import "RCTLog.h"
@implementation RCTEventEmitter
{
NSInteger _listenerCount;
}
+ (NSString *)moduleName
{
return @"";
}
+ (void)initialize
{
if (self != [RCTEventEmitter class]) {
RCTAssert(RCTClassOverridesInstanceMethod(self, @selector(supportedEvents)),
@"You must override the `supportedEvents` method of %@", self);
}
}
- (NSArray<NSString *> *)supportedEvents
{
return nil;
}
- (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);
}
}
- (void)startObserving
{
// Does nothing
}
- (void)stopObserving
{
// Does nothing
}
- (void)dealloc
{
if (_listenerCount > 0) {
[self stopObserving];
}
}
RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
{
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(@"`%@` is not a supported event type for %@. Supported events are: `%@`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:@"`, `"]);
}
_listenerCount++;
if (_listenerCount == 1) {
[self startObserving];
}
}
RCT_EXPORT_METHOD(removeListeners:(NSInteger)count)
{
if (RCT_DEBUG && count > _listenerCount) {
RCTLogError(@"Attempted to remove more %@ listeners than added", [self class]);
}
_listenerCount = MAX(_listenerCount - count, 0);
if (_listenerCount == 0) {
[self stopObserving];
}
}
@end