diff --git a/docs/development/native-event.md b/docs/development/native-event.md index 44de87bc4f8..02772a9a1ee 100644 --- a/docs/development/native-event.md +++ b/docs/development/native-event.md @@ -132,14 +132,14 @@ hippyEngine.sendEvent("rotate", hippyMap); 终端在需要发送事件的地方调用代码: ```objectivec -// 也可以参考HippyEventObserverModule.m -[self sendEvent: @"rotate" params: @{@"foo":@"bar"}]; -- (void)sendEvent:(NSString *)eventName params:(NSDictionary *)params -{ - HippyAssertParam(eventName); - // 这里的"EventDispatcher"和"receiveNativeEvent"是常量,无需也不能更改 - [self.bridge.eventDispatcher dispatchEvent:@"EventDispatcher" methodName:@"receiveNativeEvent" args:@{@"eventName": eventName, @"extra": params ? : @{}}]; -} +// 调用HippyBridge的如下实例方法,比如: +[self.hippyBridge sendEvent:@"rotate" params:@{@"foo":@"bar"}]; + +/// Send native event to JS side +/// - Parameters: +/// - eventName: event name +/// - params: event info +- (void)sendEvent:(NSString *)eventName params:(NSDictionary *_Nullable)params; ``` # Voltron diff --git a/framework/ios/base/bridge/HippyBridge.h b/framework/ios/base/bridge/HippyBridge.h index 38cb804a2be..07aea37dab7 100644 --- a/framework/ios/base/bridge/HippyBridge.h +++ b/framework/ios/base/bridge/HippyBridge.h @@ -298,7 +298,7 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); - (void)requestReload; -#pragma mark - +#pragma mark - JS Communication Related /// Access the underlying JavaScript executor. /// You can use this in unit tests to detect when the executor has been invalidated, diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index 8f7eba783fd..36bccd6d74f 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -96,15 +96,23 @@ NSString *const kHippyLaunchOptionsDebugModeKey = @"DebugMode"; NSString *const kHippyLaunchOptionsEnableTurboKey = @"EnableTurbo"; -// Global device info keys -static NSString *const HippyNativeGlobalKeyOS = @"OS"; -static NSString *const HippyNativeGlobalKeyOSVersion = @"OSVersion"; -static NSString *const HippyNativeGlobalKeyDevice = @"Device"; -static NSString *const HippyNativeGlobalKeySDKVersion = @"SDKVersion"; -static NSString *const HippyNativeGlobalKeyAppVersion = @"AppVersion"; -static NSString *const HippyNativeGlobalKeyDimensions = @"Dimensions"; -static NSString *const HippyNativeGlobalKeyLocalization = @"Localization"; -static NSString *const HippyNativeGlobalKeyNightMode = @"NightMode"; +// Global device info keys & values +static NSString *const kHippyNativeGlobalKeyOS = @"OS"; +static NSString *const kHippyNativeGlobalKeyOSVersion = @"OSVersion"; +static NSString *const kHippyNativeGlobalKeyDevice = @"Device"; +static NSString *const kHippyNativeGlobalKeySDKVersion = @"SDKVersion"; +static NSString *const kHippyNativeGlobalKeyAppVersion = @"AppVersion"; +static NSString *const kHippyNativeGlobalKeyDimensions = @"Dimensions"; +static NSString *const kHippyNativeGlobalKeyLocalization = @"Localization"; +static NSString *const kHippyNativeGlobalKeyNightMode = @"NightMode"; +static NSString *const kHippyNativeGlobalOSValue = @"ios"; +static NSString *const kHippyCFBundleShortVersionKey = @"CFBundleShortVersionString"; + +// Localization infos +static NSString *const kHippyLocalizaitionCountryKey = @"country"; +static NSString *const kHippyLocalizaitionLanguageKey = @"language"; +static NSString *const kHippyLocalizaitionDirectionKey = @"direction"; +static NSString *const kHippyLocalizaitionValueUnknown = @"unknown"; // Key of module config info for js side static NSString *const kHippyRemoteModuleConfigKey = @"remoteModuleConfig"; @@ -657,7 +665,8 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:( NSArray *moduleDataByID = [_moduleSetup moduleDataByID]; if (moduleID >= [moduleDataByID count]) { if (isValid) { - HippyLogError(@"moduleID %lu exceed range of moduleDataByID %lu, bridge is valid %ld", moduleID, [moduleDataByID count], (long)isValid); + HippyLogError(@"moduleID %lu exceed range of moduleDataByID %lu, bridge is valid %ld", + moduleID, [moduleDataByID count], (long)isValid); } return nil; } @@ -668,23 +677,19 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:( } return nil; } - // not for UI Actions if NO==_valid - if (!isValid) { - if ([[moduleData name] isEqualToString:@"UIManager"]) { - return nil; - } - } NSArray> *methods = [moduleData.methods copy]; if (methodID >= [methods count]) { if (isValid) { - HippyLogError(@"methodID %lu exceed range of moduleData.methods %lu, bridge is valid %ld", moduleID, [methods count], (long)isValid); + HippyLogError(@"methodID %lu exceed range of moduleData.methods %lu, bridge is valid %ld", + moduleID, [methods count], (long)isValid); } return nil; } id method = methods[methodID]; if (HIPPY_DEBUG && !method) { if (isValid) { - HippyLogError(@"Unknown methodID: %lu for module: %lu (%@)", (unsigned long)methodID, (unsigned long)moduleID, moduleData.name); + HippyLogError(@"Unknown methodID: %lu for module: %lu (%@)", + (unsigned long)methodID, (unsigned long)moduleID, moduleData.name); } return nil; } @@ -713,7 +718,8 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:( @throw exception; } - NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", exception, method.JSMethodName, moduleData.name, params]; + NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", + exception, method.JSMethodName, moduleData.name, params]; NSError *error = HippyErrorWithMessage(message); HippyBridgeFatal(error, self); return nil; @@ -737,7 +743,8 @@ - (id)callNativeModuleName:(NSString *)moduleName methodName:(NSString *)methodN @throw exception; } - NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", exception, method.JSMethodName, module.name, params]; + NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", + exception, method.JSMethodName, module.name, params]; HippyBridgeFatal(HippyErrorWithMessage(message), self); return nil; } @@ -842,30 +849,29 @@ - (NSDictionary *)genRawDeviceInfoDict { uname(&systemInfo); NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; NSMutableDictionary *deviceInfo = [NSMutableDictionary dictionary]; - [deviceInfo setValue:@"ios" forKey:HippyNativeGlobalKeyOS]; - [deviceInfo setValue:iosVersion forKey:HippyNativeGlobalKeyOSVersion]; - [deviceInfo setValue:deviceModel forKey:HippyNativeGlobalKeyDevice]; - [deviceInfo setValue:_HippySDKVersion forKey:HippyNativeGlobalKeySDKVersion]; - - NSString *appVer = [[NSBundle.mainBundle infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + deviceInfo[kHippyNativeGlobalKeyOS] = kHippyNativeGlobalOSValue; + deviceInfo[kHippyNativeGlobalKeyOSVersion] = iosVersion; + deviceInfo[kHippyNativeGlobalKeyDevice] = deviceModel; + deviceInfo[kHippyNativeGlobalKeySDKVersion] = _HippySDKVersion; + NSString *appVer = [[NSBundle.mainBundle infoDictionary] objectForKey:kHippyCFBundleShortVersionKey]; if (appVer) { - [deviceInfo setValue:appVer forKey:HippyNativeGlobalKeyAppVersion]; + deviceInfo[kHippyNativeGlobalKeyAppVersion] = appVer; } if (self.cachedDimensionsInfo) { - [deviceInfo setValue:self.cachedDimensionsInfo forKey:HippyNativeGlobalKeyDimensions]; + deviceInfo[kHippyNativeGlobalKeyDimensions] = self.cachedDimensionsInfo; } NSString *countryCode = [[HippyI18nUtils sharedInstance] currentCountryCode]; NSString *lanCode = [[HippyI18nUtils sharedInstance] currentAppLanguageCode]; NSWritingDirection direction = [[HippyI18nUtils sharedInstance] writingDirectionForCurrentAppLanguage]; NSDictionary *localizaitionInfo = @{ - @"country" : countryCode?:@"unknown", - @"language" : lanCode?:@"unknown", - @"direction" : @(direction) + kHippyLocalizaitionCountryKey : countryCode ?: kHippyLocalizaitionValueUnknown, + kHippyLocalizaitionLanguageKey : lanCode ?: kHippyLocalizaitionValueUnknown, + kHippyLocalizaitionDirectionKey : @(direction) }; - [deviceInfo setValue:localizaitionInfo forKey:HippyNativeGlobalKeyLocalization]; - [deviceInfo setValue:@([self isOSNightMode]) forKey:HippyNativeGlobalKeyNightMode]; + deviceInfo[kHippyNativeGlobalKeyLocalization] = localizaitionInfo; + deviceInfo[kHippyNativeGlobalKeyNightMode] = @([self isOSNightMode]); return deviceInfo; } @@ -889,14 +895,11 @@ - (void)setOSNightMode:(BOOL)isOSNightMode withRootViewTag:(nonnull NSNumber *)r _isOSNightMode = isOSNightMode; // Notify to JS Driver Side // 1. Update global object - [self.javaScriptExecutor updateNativeInfoToHippyGlobalObject:@{ HippyNativeGlobalKeyNightMode: @(isOSNightMode) }]; + [self.javaScriptExecutor updateNativeInfoToHippyGlobalObject:@{ kHippyNativeGlobalKeyNightMode: @(isOSNightMode) }]; // 2. Send event - NSDictionary *args = @{@"eventName": hippyOnNightModeChangedEvent, - @"extra": @{ hippyOnNightModeChangedParam1 : @(isOSNightMode), - hippyOnNightModeChangedParam2 : rootViewTag } }; - [self.eventDispatcher dispatchEvent:@"EventDispatcher" - methodName:@"receiveNativeEvent" args:args]; + [self sendEvent:hippyOnNightModeChangedEvent params:@{ hippyOnNightModeChangedParam1 : @(isOSNightMode), + hippyOnNightModeChangedParam2 : rootViewTag }]; } @@ -946,9 +949,7 @@ - (void)setContextName:(NSString *)contextName { } - (void)sendEvent:(NSString *)eventName params:(NSDictionary *_Nullable)params { - [self.eventDispatcher dispatchEvent:@"EventDispatcher" - methodName:@"receiveNativeEvent" - args:@{@"eventName": eventName, @"extra": params ? : @{}}]; + [self.eventDispatcher dispatchNativeEvent:eventName withParams:params]; } diff --git a/framework/ios/base/modules/HippyEventDispatcher.h b/framework/ios/base/modules/HippyEventDispatcher.h index 065474e1760..08b2d404775 100644 --- a/framework/ios/base/modules/HippyEventDispatcher.h +++ b/framework/ios/base/modules/HippyEventDispatcher.h @@ -21,21 +21,9 @@ */ #import - #import "HippyBridge.h" -/** - * The threshold at which text inputs will start warning that the JS thread - * has fallen behind (resulting in poor input performance, missed keys, etc.) - */ -HIPPY_EXTERN const NSInteger HippyTextUpdateLagWarningThreshold; - -/** - * Takes an input event name and normalizes it to the form that is required - * by the events system (currently that means starting with the "top" prefix, - * but that's an implementation detail that may change in future). - */ -HIPPY_EXTERN NSString *HippyNormalizeInputEventName(NSString *eventName); +NS_ASSUME_NONNULL_BEGIN /** * This class wraps the -[HippyBridge enqueueJSCall:args:] method, and @@ -43,12 +31,22 @@ HIPPY_EXTERN NSString *HippyNormalizeInputEventName(NSString *eventName); */ @interface HippyEventDispatcher : NSObject +/// Send event to JS side with given params. - (void)dispatchEvent:(NSString *)moduleName methodName:(NSString *)methodName args:(NSDictionary *)params; +/// Similar to the above `dispatchEvent` method, but designed to send Native events only. +/// - Parameters: +/// - eventName: name of event +/// - params: event params +- (void)dispatchNativeEvent:(NSString *)eventName withParams:(nullable NSDictionary *)params; + @end @interface HippyBridge (HippyEventDispatcher) +/// A dispatcher responsible for sending event to js side. - (HippyEventDispatcher *)eventDispatcher; @end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/modules/HippyEventDispatcher.mm b/framework/ios/base/modules/HippyEventDispatcher.mm index 3845153114a..fca56878d20 100644 --- a/framework/ios/base/modules/HippyEventDispatcher.mm +++ b/framework/ios/base/modules/HippyEventDispatcher.mm @@ -25,17 +25,19 @@ #import "HippyUtils.h" #import "HippyBridge+ModuleManage.h" -const NSInteger HippyTextUpdateLagWarningThreshold = 3; - -NSString *HippyNormalizeInputEventName(NSString *eventName) { - if ([eventName hasPrefix:@"on"]) { - eventName = [eventName stringByReplacingCharactersInRange:(NSRange) { 0, 2 } withString:@"top"]; - } else if (![eventName hasPrefix:@"top"]) { - eventName = [[@"top" stringByAppendingString:[eventName substringToIndex:1].uppercaseString] - stringByAppendingString:[eventName substringFromIndex:1]]; - } - return eventName; -} +static NSString *const kHippyCallJSModuleKey = @"callJsModule"; +static NSString *const kHippyEventDispatcherModuleNameKey = @"moduleName"; +static NSString *const kHippyEventDispatcherMethodNameKey = @"methodName"; +static NSString *const kHippyEventDispatcherParamsKey = @"params"; + +static NSString *const kHippyEventDispatcherModule = @"EventDispatcher"; +static NSString *const kHippyReceiveNativeEventMethod = @"receiveNativeEvent"; +static NSString *const kHippyReceiveUIEventMethod = @"receiveUIComponentEvent"; +static NSString *const kHippyReceiveGestureEventMethod = @"receiveNativeGesture"; +static NSString *const kHippyEventNameKey = @"eventName"; +static NSString *const kHippyEventParamsKey = @"extra"; +static NSString *const kHippyEventIdKey = @"id"; + @implementation HippyEventDispatcher @@ -44,36 +46,35 @@ @implementation HippyEventDispatcher HIPPY_EXPORT_MODULE() - (void)dispatchEvent:(NSString *)moduleName methodName:(NSString *)methodName args:(NSDictionary *)params { - NSString *action = @"callJsModule"; NSMutableArray *events = [NSMutableArray array]; - [events addObject:action]; + [events addObject:kHippyCallJSModuleKey]; NSMutableDictionary *body = [NSMutableDictionary new]; - [body setObject:moduleName forKey:@"moduleName"]; - [body setObject:methodName forKey:@"methodName"]; - - if ([moduleName isEqualToString:@"EventDispatcher"] && params) { - NSNumber *tag = params[@"id"]; - NSString *eventName = params[@"eventName"] ?: @""; - NSDictionary *extra = params[@"extra"] ?: @{}; - if ([methodName isEqualToString:@"receiveNativeEvent"]) { + [body setObject:moduleName forKey:kHippyEventDispatcherModuleNameKey]; + [body setObject:methodName forKey:kHippyEventDispatcherMethodNameKey]; + + if ([moduleName isEqualToString:kHippyEventDispatcherModule] && params) { + NSString *eventName = params[kHippyEventNameKey] ?: @""; + NSDictionary *extra = params[kHippyEventParamsKey] ?: @{}; + if ([methodName isEqualToString:kHippyReceiveNativeEventMethod]) { NSMutableArray *detail = [NSMutableArray new]; [detail addObject:eventName]; [detail addObject:extra]; - [body setValue:detail forKey:@"params"]; - } else if ([methodName isEqualToString:@"receiveUIComponentEvent"]) { + [body setValue:detail forKey:kHippyEventDispatcherParamsKey]; + } else if ([methodName isEqualToString:kHippyReceiveUIEventMethod]) { + NSNumber *tag = params[kHippyEventIdKey]; NSMutableArray *detail = [NSMutableArray new]; if (tag) { [detail addObject:tag]; } [detail addObject:eventName]; [detail addObject:extra]; - [body setValue:detail forKey:@"params"]; - } else if ([methodName isEqualToString:@"receiveNativeGesture"]) { - [body setValue:params forKey:@"params"]; + [body setValue:detail forKey:kHippyEventDispatcherParamsKey]; + } else if ([methodName isEqualToString:kHippyReceiveGestureEventMethod]) { + [body setValue:params forKey:kHippyEventDispatcherParamsKey]; } } else { - [body setValue:params forKey:@"params"]; + [body setValue:params forKey:kHippyEventDispatcherParamsKey]; } [events addObject:body]; @@ -81,6 +82,22 @@ - (void)dispatchEvent:(NSString *)moduleName methodName:(NSString *)methodName a [_bridge enqueueJSCall:moduleName method:methodName args:events completion:NULL]; } +- (void)dispatchNativeEvent:(NSString *)eventName withParams:(NSDictionary *)params { + NSMutableDictionary *body = [NSMutableDictionary new]; + body[kHippyEventDispatcherModuleNameKey] = kHippyEventDispatcherModule; + body[kHippyEventDispatcherMethodNameKey] = kHippyReceiveNativeEventMethod; + body[kHippyEventDispatcherParamsKey] = @[ (eventName ?: @""), (params ?: @{}) ]; + + NSMutableArray *events = [NSMutableArray array]; + [events addObject:kHippyCallJSModuleKey]; + [events addObject:body]; + + [_bridge enqueueJSCall:kHippyEventDispatcherModule + method:kHippyReceiveNativeEventMethod + args:events + completion:nil]; +} + - (dispatch_queue_t)methodQueue { return HippyJSThread; } diff --git a/framework/ios/module/eventobserver/HippyEventObserverModule.mm b/framework/ios/module/eventobserver/HippyEventObserverModule.mm index 1e97aa45468..b3726aa0339 100644 --- a/framework/ios/module/eventobserver/HippyEventObserverModule.mm +++ b/framework/ios/module/eventobserver/HippyEventObserverModule.mm @@ -32,13 +32,11 @@ @implementation HippyEventObserverModule { HIPPY_EXPORT_MODULE(EventObserver) -- (dispatch_queue_t)methodQueue -{ +- (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } -- (instancetype)init -{ +- (instancetype)init { if (self = [super init]) { _config = [NSMutableDictionary new]; } @@ -68,26 +66,18 @@ - (instancetype)init } } -- (void)addEventObserverForName:(__unused NSString *)eventName -{ +- (void)addEventObserverForName:(__unused NSString *)eventName { // should override by subclass // do sth } -- (void)removeEventObserverForName:(__unused NSString *)eventName -{ +- (void)removeEventObserverForName:(__unused NSString *)eventName { // should override by subclass // do sth } -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver: self]; -} - -- (void)sendEvent:(NSString *)eventName params:(NSDictionary *)params -{ +- (void)sendEvent:(NSString *)eventName params:(NSDictionary *)params { HippyAssertParam(eventName); - [self.bridge.eventDispatcher dispatchEvent:@"EventDispatcher" methodName:@"receiveNativeEvent" args:@{@"eventName": eventName, @"extra": params ? : @{}}]; + [self.bridge.eventDispatcher dispatchNativeEvent:eventName withParams:params]; } @end diff --git a/tests/ios/HippyEventDispatcherTest.m b/tests/ios/HippyEventDispatcherTest.m new file mode 100644 index 00000000000..d8ff7310495 --- /dev/null +++ b/tests/ios/HippyEventDispatcherTest.m @@ -0,0 +1,66 @@ +/*! + * iOS SDK + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import +#import +#import + +@interface HippyEventDispatcherTest : XCTestCase + +@end + +@implementation HippyEventDispatcherTest + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testBridgeEventDispatcherModule { + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; + HippyBridge *mockBridge = OCMPartialMock(bridge); + HippyEventDispatcher *dispatcher = mockBridge.eventDispatcher; + XCTAssertNotNil(dispatcher); + + NSString *testEvent = @"testEvent"; + NSDictionary *testParams = @{ @"testKey" : @YES }; + static NSString *const kHippyEventDispatcherModule = @"EventDispatcher"; + static NSString *const kHippyReceiveNativeEventMethod = @"receiveNativeEvent"; + [dispatcher dispatchNativeEvent:testEvent withParams:testParams]; + OCMVerify([mockBridge enqueueJSCall:kHippyEventDispatcherModule + method:kHippyReceiveNativeEventMethod + args:OCMArg.any completion:nil]); + + [dispatcher dispatchEvent:kHippyEventDispatcherModule + methodName:kHippyReceiveNativeEventMethod + args:testParams]; + OCMVerify([mockBridge enqueueJSCall:kHippyEventDispatcherModule + method:kHippyReceiveNativeEventMethod + args:OCMArg.any completion:nil]); +} + + +@end