Skip to content

Commit

Permalink
BREAKING - RCTEvent improvements, remove deprecated [sendInputEventWi…
Browse files Browse the repository at this point in the history
…thName:body:] (#15894)

Summary:
This makes the RCTEvent protocol more generic to make it easier to use the event coalescing feature for type of events other than components. This does a few other improvements that will be useful in follow up PRs.

- Add `RCTComponentEvent` which is used instead of deprecated `[sendInputEventWithName:body:]` and remove that method completely (was only used at 2 places).
- Make `coalescingKey` optional for events that return NO from `canCoalesce`.
- Make `viewTag` optional for events that are not related to views.
- Fast path for events that return NO from `canCoalesce`.
- Add a missing test for event coalescing with different view tags.

Ended up making only one PR for all this since the changes are related and hard to separate.

**Migration**
Use a custom RCTEvent subclass with `[sendEvent:]` (preferred way to allow type safe events) or `RCTComponentEvent`.

**Test plan**
- Ran RCTEventDispatcher unit tests
- Tested manually in RNTester

Changelog:

[iOS] [Changed] - Remove deprecated RCTEvent method, sendInputEventWithName:body:
Pull Request resolved: #15894

Reviewed By: shergin

Differential Revision: D13726194

Pulled By: hramos

fbshipit-source-id: 11f63a99e08f46ec6b4f16f8d9949cdbf5c3fe13
  • Loading branch information
janicduplessis authored and facebook-github-bot committed Mar 27, 2019
1 parent 456c03a commit 41343f6
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 63 deletions.
29 changes: 29 additions & 0 deletions RNTester/RNTesterUnitTests/RCTEventDispatcherTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,35 @@ - (void)testDifferentEventTypesDontCoalesce
[_bridge verify];
}

- (void)testDifferentViewTagsDontCoalesce
{
RCTTestEvent *firstEvent = [[RCTTestEvent alloc] initWithViewTag:@(1)
eventName:_eventName
body:_body
coalescingKey:0];
RCTTestEvent *secondEvent = [[RCTTestEvent alloc] initWithViewTag:@(2)
eventName:_eventName
body:_body
coalescingKey:0];

__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
[[_bridge expect] enqueueJSCall:[[firstEvent class] moduleDotMethod]
args:[firstEvent arguments]];
[[_bridge expect] enqueueJSCall:[[secondEvent class] moduleDotMethod]
args:[secondEvent arguments]];


[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:secondEvent];
eventsEmittingBlock();

[_bridge verify];
}

- (void)testSameEventTypesWithDifferentCoalesceKeysDontCoalesce
{
NSString *eventName = RCTNormalizeInputEventName(@"firstEvent");
Expand Down
19 changes: 19 additions & 0 deletions React/Base/RCTComponentEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affilities.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <React/RCTEventDispatcher.h>

/**
* Generic untyped event for Components. Used internally by RCTDirectEventBlock and
* RCTBubblingEventBlock, for other use cases prefer using a class that implements
* RCTEvent to have a type safe way to initialize it.
*/
@interface RCTComponentEvent : NSObject<RCTEvent>

- (instancetype)initWithName:(NSString *)name viewTag:(NSNumber *)viewTag body:(NSDictionary *)body;

@end
50 changes: 50 additions & 0 deletions React/Base/RCTComponentEvent.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) Facebook, Inc. and its affilities.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import "RCTComponentEvent.h"

#import "RCTAssert.h"

@implementation RCTComponentEvent
{
NSArray *_arguments;
}

@synthesize eventName = _eventName;
@synthesize viewTag = _viewTag;

- (instancetype)initWithName:(NSString *)name viewTag:(NSNumber *)viewTag body:(NSDictionary *)body
{
if (self = [super init]) {
NSMutableDictionary *mutableBody = [NSMutableDictionary dictionaryWithDictionary:body];
mutableBody[@"target"] = viewTag;

_eventName = RCTNormalizeInputEventName(name);
_viewTag = viewTag;
_arguments = @[_viewTag, _eventName, mutableBody];
}
return self;
}

RCT_NOT_IMPLEMENTED(- (instancetype)init)

- (NSArray *)arguments
{
return _arguments;
}

- (BOOL)canCoalesce
{
return NO;
}

+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}

@end
29 changes: 18 additions & 11 deletions React/Base/RCTEventDispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,31 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
@protocol RCTEvent <NSObject>
@required

@property (nonatomic, strong, readonly) NSNumber *viewTag;
@property (nonatomic, copy, readonly) NSString *eventName;
@property (nonatomic, assign, readonly) uint16_t coalescingKey;

- (BOOL)canCoalesce;
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;

// used directly for doing a JS call
/** used directly for doing a JS call */
+ (NSString *)moduleDotMethod;
// must contain only JSON compatible values

/** must contain only JSON compatible values */
- (NSArray *)arguments;

@optional

/**
* Can be implemented for view based events that need to be coalesced
* by it's viewTag.
*/
@property (nonatomic, strong, readonly) NSNumber *viewTag;

/**
* Coalescing related methods must only be implemented if canCoalesce
* returns YES.
*/
@property (nonatomic, assign, readonly) uint16_t coalescingKey;
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;

@end

/**
Expand Down Expand Up @@ -81,12 +94,6 @@ __deprecated_msg("Subclass RCTEventEmitter instead");
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
__deprecated_msg("Subclass RCTEventEmitter instead");

/**
* Deprecated, do not use.
*/
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
__deprecated_msg("Use RCTDirectEventBlock or RCTBubblingEventBlock instead");

/**
* Send a text input/focus event. For internal use only.
*/
Expand Down
79 changes: 33 additions & 46 deletions React/Base/RCTEventDispatcher.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTUtils.h"
#import "RCTComponentEvent.h"
#import "RCTProfile.h"
#import "RCTUtils.h"

const NSInteger RCTTextUpdateLagWarningThreshold = 3;

Expand All @@ -29,7 +30,7 @@
static NSNumber *RCTGetEventID(id<RCTEvent> event)
{
return @(
event.viewTag.intValue |
([event respondsToSelector:@selector(viewTag)] ? event.viewTag.intValue : 0) |
(((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
(((uint64_t)event.coalescingKey) << 48)
);
Expand Down Expand Up @@ -79,24 +80,6 @@ - (void)sendDeviceEventWithName:(NSString *)name body:(id)body
completion:NULL];
}

- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{
if (RCT_DEBUG) {
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a React tag");
}

if (!body[@"target"]) {
return;
}

name = RCTNormalizeInputEventName(name);
[_bridge enqueueJSCall:@"RCTEventEmitter"
method:@"receiveEvent"
args:@[body[@"target"], name, body]
completion:NULL];
}

- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text
Expand All @@ -114,7 +97,6 @@ - (void)sendTextEventWithType:(RCTTextEventType)type

NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithDictionary:@{
@"eventCount": @(eventCount),
@"target": reactTag
}];

if (text) {
Expand All @@ -138,10 +120,10 @@ - (void)sendTextEventWithType:(RCTTextEventType)type
body[@"key"] = key;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self sendInputEventWithName:events[type] body:body];
#pragma clang diagnostic pop
RCTComponentEvent *event = [[RCTComponentEvent alloc] initWithName:events[type]
viewTag:reactTag
body:body];
[self sendEvent:event];
}

- (void)sendEvent:(id<RCTEvent>)event
Expand All @@ -154,33 +136,38 @@ - (void)sendEvent:(id<RCTEvent>)event

[_observersLock unlock];

[_eventQueueLock lock];
if (event.canCoalesce) {
[_eventQueueLock lock];

NSNumber *eventID = RCTGetEventID(event);
NSNumber *eventID = RCTGetEventID(event);

id<RCTEvent> previousEvent = _events[eventID];
if (previousEvent) {
RCTAssert([event canCoalesce], @"Got event %@ which cannot be coalesced, but has the same eventID %@ as the previous event %@", event, eventID, previousEvent);
event = [previousEvent coalesceWithEvent:event];
} else {
[_eventQueue addObject:eventID];
}
_events[eventID] = event;
id<RCTEvent> previousEvent = _events[eventID];
if (previousEvent) {
event = [previousEvent coalesceWithEvent:event];
} else {
[_eventQueue addObject:eventID];
}
_events[eventID] = event;

BOOL scheduleEventsDispatch = NO;
if (!_eventsDispatchScheduled) {
_eventsDispatchScheduled = YES;
scheduleEventsDispatch = YES;
}
BOOL scheduleEventsDispatch = NO;
if (!_eventsDispatchScheduled) {
_eventsDispatchScheduled = YES;
scheduleEventsDispatch = YES;
}

// We have to release the lock before dispatching block with events,
// since dispatchBlock: can be executed synchronously on the same queue.
// (This is happening when chrome debugging is turned on.)
[_eventQueueLock unlock];
// We have to release the lock before dispatching block with events,
// since dispatchBlock: can be executed synchronously on the same queue.
// (This is happening when chrome debugging is turned on.)
[_eventQueueLock unlock];

if (scheduleEventsDispatch) {
if (scheduleEventsDispatch) {
[_bridge dispatchBlock:^{
[self flushEventsQueue];
} queue:RCTJSThread];
}
} else {
[_bridge dispatchBlock:^{
[self flushEventsQueue];
[self dispatchEvent:event];
} queue:RCTJSThread];
}
}
Expand Down
15 changes: 15 additions & 0 deletions React/React.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; };
191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; };
191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; };
1968A25F1F67275300EB3D1D /* RCTComponentEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 1968A25D1F67275300EB3D1D /* RCTComponentEvent.h */; };
1968A2601F67275300EB3D1D /* RCTComponentEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 1968A25D1F67275300EB3D1D /* RCTComponentEvent.h */; };
1968A2611F67275300EB3D1D /* RCTComponentEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 1968A25E1F67275300EB3D1D /* RCTComponentEvent.m */; };
1968A2621F67275300EB3D1D /* RCTComponentEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 1968A25E1F67275300EB3D1D /* RCTComponentEvent.m */; };
199B8A6F1F44DB16005DEF67 /* RCTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 199B8A6E1F44DB16005DEF67 /* RCTVersion.h */; };
199B8A761F44DEDA005DEF67 /* RCTVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 199B8A6E1F44DB16005DEF67 /* RCTVersion.h */; };
19F61BFA1E8495CD00571D81 /* bignum-dtoa.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 139D7E3A1E25C5A300323FB7 /* bignum-dtoa.h */; };
Expand Down Expand Up @@ -1991,6 +1995,8 @@
191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControlManager.m; sourceTree = "<group>"; };
191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControl.h; sourceTree = "<group>"; };
191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControl.m; sourceTree = "<group>"; };
1968A25D1F67275300EB3D1D /* RCTComponentEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponentEvent.h; sourceTree = "<group>"; };
1968A25E1F67275300EB3D1D /* RCTComponentEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTComponentEvent.m; sourceTree = "<group>"; };
199B8A6E1F44DB16005DEF67 /* RCTVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVersion.h; sourceTree = "<group>"; };
27B958731E57587D0096647A /* JSBigString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSBigString.cpp; sourceTree = "<group>"; };
2D2A28131D9B038B00D4039D /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -2908,6 +2914,8 @@
830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */,
68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */,
1968A25D1F67275300EB3D1D /* RCTComponentEvent.h */,
1968A25E1F67275300EB3D1D /* RCTComponentEvent.m */,
83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
C60128A91F3D1258009DF9FF /* RCTCxxConvert.h */,
Expand Down Expand Up @@ -3223,6 +3231,10 @@
594F0A331FD23228007FBE96 /* RCTSurfaceHostingView.h in Headers */,
130443DD1E401AF500D93A67 /* RCTConvert+Transform.h in Headers */,
134D63C41F1FEC65008872B5 /* RCTCxxBridgeDelegate.h in Headers */,
3D302F801DF828F800D6DDAE /* RCTNavItem.h in Headers */,
1968A2601F67275300EB3D1D /* RCTComponentEvent.h in Headers */,
3D302F811DF828F800D6DDAE /* RCTNavItemManager.h in Headers */,
135A9C061E7B0F7800587AEB /* RCTJSCHelpers.h in Headers */,
3D302F841DF828F800D6DDAE /* RCTPointerEvents.h in Headers */,
59EB6DBC1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h in Headers */,
657734941EE8356100A0E9EA /* RCTInspectorPackagerConnection.h in Headers */,
Expand Down Expand Up @@ -3396,6 +3408,7 @@
599FAA361FB274980058CCF6 /* RCTSurface.h in Headers */,
3D80DA231DF820620028D040 /* RCTBridgeDelegate.h in Headers */,
3D80DA241DF820620028D040 /* RCTBridgeMethod.h in Headers */,
1968A25F1F67275300EB3D1D /* RCTComponentEvent.h in Headers */,
3D7BFD151EA8E351008DFB7A /* RCTPackagerClient.h in Headers */,
3D80DA251DF820620028D040 /* RCTBridgeModule.h in Headers */,
3D80DA261DF820620028D040 /* RCTBundleURLProvider.h in Headers */,
Expand Down Expand Up @@ -4212,6 +4225,7 @@
2D3B5EE41D9B09BB00451313 /* RCTSegmentedControlManager.m in Sources */,
59EDBCB81FDF4E0C003573DE /* RCTScrollView.m in Sources */,
13134C9F1E296B2A00B9F3CB /* RCTCxxModule.mm in Sources */,
1968A2621F67275300EB3D1D /* RCTComponentEvent.m in Sources */,
2D3B5EE31D9B09B700451313 /* RCTSegmentedControl.m in Sources */,
130443A41E3FEAC600D93A67 /* RCTFollyConvert.mm in Sources */,
3D7BFD201EA8E351008DFB7A /* RCTPackagerConnection.mm in Sources */,
Expand Down Expand Up @@ -4469,6 +4483,7 @@
59283CA01FD67321000EAAB9 /* RCTSurfaceStage.m in Sources */,
13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */,
14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */,
1968A2611F67275300EB3D1D /* RCTComponentEvent.m in Sources */,
3D7BFD171EA8E351008DFB7A /* RCTPackagerClient.m in Sources */,
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
13134C8E1E296B2A00B9F3CB /* RCTMessageThread.mm in Sources */,
Expand Down
11 changes: 5 additions & 6 deletions React/Views/RCTComponentData.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#import "RCTBridge.h"
#import "RCTBridgeModule.h"
#import "RCTComponentEvent.h"
#import "RCTConvert.h"
#import "RCTParserUtils.h"
#import "RCTShadowView.h"
Expand Down Expand Up @@ -114,12 +115,10 @@ static RCTPropBlock createEventSetter(NSString *propName, SEL setter, RCTBridge
return;
}

NSMutableDictionary *mutableEvent = [NSMutableDictionary dictionaryWithDictionary:event];
mutableEvent[@"target"] = strongTarget.reactTag;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[weakBridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(propName) body:mutableEvent];
#pragma clang diagnostic pop
RCTComponentEvent *componentEvent = [[RCTComponentEvent alloc] initWithName:propName
viewTag:strongTarget.reactTag
body:event];
[weakBridge.eventDispatcher sendEvent:componentEvent];
};
}
((void (*)(id, SEL, id))objc_msgSend)(target, setter, eventHandler);
Expand Down

0 comments on commit 41343f6

Please sign in to comment.