From cae4761006266c4fdbfd347502dce2bcf0efe566 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 23 Oct 2015 10:14:26 -0700 Subject: [PATCH] Use arrays for module method data Summary: public Use arrays instead of dictionaries for encoding module method information. This further reduces UIExplorer startup JSON from 16104 bytes to 14119 (12% reduction) Reviewed By: javache Differential Revision: D2570057 fb-gh-sync-id: 4a53a9ead4365a136e7caeb650375733e1c24c0e --- .../UIExplorerUnitTests/RCTBridgeTests.m | 48 ++++--- Libraries/Utilities/MessageQueue.js | 135 ++++++++++++++---- React/Base/RCTBatchedBridge.m | 7 +- React/Base/RCTModuleData.h | 2 +- React/Base/RCTModuleData.m | 46 +++--- React/Base/RCTModuleMethod.h | 1 - React/Base/RCTModuleMethod.m | 1 + 7 files changed, 161 insertions(+), 79 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index a3c4c4354f..7802f4ad18 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -136,19 +136,25 @@ _Pragma("clang diagnostic pop") NSString *injectedStuff; RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"])); - NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL); - NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"]; - NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"]; - NSDictionary *constants = testModuleConfig[@"constants"]; - NSDictionary *methods = testModuleConfig[@"methods"]; + __block NSNumber *testModuleID = nil; + __block NSDictionary *testConstants = nil; + __block NSNumber *testMethodID = nil; + + NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; + [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) { + if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) { + testModuleID = @(i); + testConstants = moduleConfig[1]; + testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]); + *stop = YES; + } + }]; - XCTAssertNotNil(moduleConfig); XCTAssertNotNil(remoteModuleConfig); - XCTAssertNotNil(testModuleConfig); - XCTAssertNotNil(constants); - XCTAssertEqualObjects(constants[@"eleventyMillion"], @42); - XCTAssertNotNil(methods); - XCTAssertNotNil(methods[@"testMethod"]); + XCTAssertNotNil(testModuleID); + XCTAssertNotNil(testConstants); + XCTAssertEqualObjects(testConstants[@"eleventyMillion"], @42); + XCTAssertNotNil(testMethodID); } - (void)testCallNativeMethod @@ -158,13 +164,19 @@ _Pragma("clang diagnostic pop") NSString *injectedStuff; RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"])); - NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL); - NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"]; - NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"]; - NSNumber *testModuleID = testModuleConfig[@"moduleID"]; - NSDictionary *methods = testModuleConfig[@"methods"]; - NSDictionary *testMethod = methods[@"testMethod"]; - NSNumber *testMethodID = testMethod[@"methodID"]; + __block NSNumber *testModuleID = nil; + __block NSDictionary *testConstants = nil; + __block NSNumber *testMethodID = nil; + + NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; + [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { + if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) { + testModuleID = @(i); + testConstants = moduleConfig[1]; + testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]); + *stop = YES; + } + }]; NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567]; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index d37881d91c..b93885d0e2 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -30,7 +30,6 @@ let MIN_TIME_BETWEEN_FLUSHES_MS = 5; let SPY_MODE = false; let MethodTypes = keyMirror({ - local: null, remote: null, remoteAsync: null, }); @@ -62,15 +61,18 @@ class MessageQueue { 'flushedQueue', ].forEach((fn) => this[fn] = this[fn].bind(this)); - this._genModules(remoteModules); + let modulesConfig = this._genModulesConfig(remoteModules); + this._genModules(modulesConfig); localModules && this._genLookupTables( - localModules, this._moduleTable, this._methodTable); + this._genModulesConfig(localModules),this._moduleTable, this._methodTable + ); this._debugInfo = {}; this._remoteModuleTable = {}; this._remoteMethodTable = {}; this._genLookupTables( - remoteModules, this._remoteModuleTable, this._remoteMethodTable); + modulesConfig, this._remoteModuleTable, this._remoteMethodTable + ); } /** @@ -182,43 +184,118 @@ class MessageQueue { /** * Private helper methods */ - _genLookupTables(localModules, moduleTable, methodTable) { - let moduleNames = Object.keys(localModules); - for (var i = 0, l = moduleNames.length; i < l; i++) { - let moduleName = moduleNames[i]; - let methods = localModules[moduleName].methods || {}; - let moduleID = localModules[moduleName].moduleID; + + /** + * Converts the old, object-based module structure to the new + * array-based structure. TODO (t8823865) Removed this + * functin once Android has been updated. + */ + _genModulesConfig(modules /* array or object */) { + if (Array.isArray(modules)) { + return modules; + } else { + let moduleArray = []; + let moduleNames = Object.keys(modules); + for (var i = 0, l = moduleNames.length; i < l; i++) { + let moduleName = moduleNames[i]; + let moduleConfig = modules[moduleName]; + let module = [moduleName]; + if (moduleConfig.constants) { + module.push(moduleConfig.constants); + } + let methodsConfig = moduleConfig.methods; + if (methodsConfig) { + let methods = []; + let asyncMethods = []; + let methodNames = Object.keys(methodsConfig); + for (var j = 0, ll = methodNames.length; j < ll; j++) { + let methodName = methodNames[j]; + let methodConfig = methodsConfig[methodName]; + methods[methodConfig.methodID] = methodName; + if (methodConfig.type === MethodTypes.remoteAsync) { + asyncMethods.push(methodConfig.methodID); + } + } + if (methods.length) { + module.push(methods); + if (asyncMethods.length) { + module.push(asyncMethods); + } + } + } + moduleArray[moduleConfig.moduleID] = module; + } + return moduleArray; + } + } + + _genLookupTables(modulesConfig, moduleTable, methodTable) { + for (var moduleID = 0, l = modulesConfig.length; moduleID < l; moduleID++) { + let module = modulesConfig[moduleID]; + if (!module) { + continue; + } + let moduleName = module[0]; moduleTable[moduleID] = moduleName; methodTable[moduleID] = {}; - - let methodNames = Object.keys(methods); - for (var j = 0, k = methodNames.length; j < k; j++) { - let methodName = methodNames[j]; - let methodConfig = methods[methodName]; - methodTable[moduleID][methodConfig.methodID] = methodName; + if (module.length > 1) { + let methodsIndex = 1; + if (!Array.isArray(module[1])) { + methodsIndex = 2; + } + if (module.length > methodsIndex) { + let methods = module[methodsIndex]; + for (var methodID = 0, ll = methods.length; methodID < ll; methodID++) { + methodTable[moduleID][methodID] = methods[methodID]; + } + } } } } _genModules(remoteModules) { - let moduleNames = Object.keys(remoteModules); - for (var i = 0, l = moduleNames.length; i < l; i++) { - let moduleName = moduleNames[i]; - let moduleConfig = remoteModules[moduleName]; + for (var i = 0, l = remoteModules.length; i < l; i++) { + let module = remoteModules[i]; + if (!module) { + continue; + } + let moduleName = module[0]; + let constants = null; + let methods = null; + let asyncMethods = null; + if (module.length > 0) { + let methodsIndex = 1; + if (!Array.isArray(module[1])) { + constants = module[1]; + methodsIndex = 2; + } + if (module.length > methodsIndex) { + methods = module[methodsIndex]; + if (module.length > methodsIndex) { + asyncMethods = module[methodsIndex]; + } + } + } + let moduleConfig = { + moduleID: i, + constants, + methods, + asyncMethods, + }; this.RemoteModules[moduleName] = this._genModule({}, moduleConfig); } } _genModule(module, moduleConfig) { - let methods = moduleConfig.methods || {}; - let methodNames = Object.keys(methods); - for (var i = 0, l = methodNames.length; i < l; i++) { - let methodName = methodNames[i]; - let methodConfig = methods[methodName]; + let methods = moduleConfig.methods || []; + let asyncMethods = moduleConfig.asyncMethods || []; + for (var methodID = 0, l = methods.length; methodID < l; methodID++) { + let methodName = methods[methodID]; + let isAsync = (asyncMethods.indexOf(methodID) !== -1); module[methodName] = this._genMethod( moduleConfig.moduleID, - methodConfig.methodID, - methodConfig.type || MethodTypes.remote + methodID, + isAsync ? MethodTypes.remoteAsync : MethodTypes.remote ); } Object.assign(module, moduleConfig.constants); @@ -226,10 +303,6 @@ class MessageQueue { } _genMethod(module, method, type) { - if (type === MethodTypes.local) { - return null; - } - let fn = null; let self = this; if (type === MethodTypes.remoteAsync) { diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index a85b09338d..11d94271c2 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -308,12 +308,9 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (NSString *)moduleConfig { - NSMutableDictionary *config = [NSMutableDictionary new]; + NSMutableArray *config = [NSMutableArray new]; for (RCTModuleData *moduleData in _moduleDataByID) { - NSDictionary *moduleConfig = moduleData.config; - if (moduleConfig) { - config[moduleData.name] = moduleConfig; - } + [config addObject:moduleData.config]; if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { [_frameUpdateObservers addObject:moduleData]; diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index 310dd2fc73..d01ccbdd70 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -20,7 +20,7 @@ @property (nonatomic, strong, readonly) Class moduleClass; @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, copy, readonly) NSArray *methods; -@property (nonatomic, copy, readonly) NSDictionary *config; +@property (nonatomic, copy, readonly) NSArray *config; @property (nonatomic, strong) dispatch_queue_t queue; diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index a95d79dd66..9dc96c7e6a 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -12,6 +12,7 @@ #import "RCTBridge.h" #import "RCTModuleMethod.h" #import "RCTLog.h" +#import "RCTUtils.h" @implementation RCTModuleData { @@ -79,37 +80,36 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); return _methods; } -- (NSDictionary *)config +- (NSArray *)config { if (_constants.count == 0 && self.methods.count == 0) { - return nil; // Nothing to export + return (id)kCFNull; // Nothing to export } - NSMutableDictionary *config = [NSMutableDictionary new]; - config[@"moduleID"] = _moduleID; - - if (_constants) { - config[@"constants"] = _constants; - } - - NSMutableDictionary *methodconfig = [NSMutableDictionary new]; - [self.methods enumerateObjectsUsingBlock:^(id method, NSUInteger idx, __unused BOOL *stop) { + NSMutableArray *methods = self.methods.count ? [NSMutableArray new] : nil; + NSMutableArray *asyncMethods = nil; + for (id method in self.methods) { + [methods addObject:method.JSMethodName]; if (method.functionType == RCTFunctionTypePromise) { - methodconfig[method.JSMethodName] = @{ - @"methodID": @(idx), - @"type": @"remoteAsync", - }; - } else { - methodconfig[method.JSMethodName] = @{ - @"methodID": @(idx), - }; + if (!asyncMethods) { + asyncMethods = [NSMutableArray new]; + } + [asyncMethods addObject:@(methods.count)]; } - }]; - if (methodconfig.count) { - config[@"methods"] = [methodconfig copy]; } - return [config copy]; + NSMutableArray *config = [NSMutableArray new]; + [config addObject:_name]; + if (_constants.count) { + [config addObject:_constants]; + } + if (methods) { + [config addObject:methods]; + if (asyncMethods) { + [config addObject:asyncMethods]; + } + } + return config; } - (dispatch_queue_t)queue diff --git a/React/Base/RCTModuleMethod.h b/React/Base/RCTModuleMethod.h index ff5c1cda64..f450c412ce 100644 --- a/React/Base/RCTModuleMethod.h +++ b/React/Base/RCTModuleMethod.h @@ -31,7 +31,6 @@ typedef NS_ENUM(NSUInteger, RCTNullability) { @property (nonatomic, readonly) Class moduleClass; @property (nonatomic, readonly) SEL selector; -@property (nonatomic, readonly) RCTFunctionType functionType; - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName JSMethodName:(NSString *)JSMethodName diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index e684f79a88..efc0076ef3 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -54,6 +54,7 @@ typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); } @synthesize JSMethodName = _JSMethodName; +@synthesize functionType = _functionType; static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index, id valueOrType, const char *issue)