/*! * 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 "HPAsserts.h" #import "HippyBridge.h" #import "HippyModuleData.h" #import "HippyModulesSetup.h" #import "HippyTurboModule.h" #import "HPLog.h" #import "HPToolUtils.h" #include "objc/runtime.h" static NSMutableArray *HippyModuleClasses; NSArray *HippyGetModuleClasses(void) { return HippyModuleClasses; } /** * Register the given class as a bridge module. All modules must be registered * prior to the first bridge initialization. */ HP_EXTERN void HippyRegisterModule(Class); void HippyRegisterModule(Class moduleClass) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ HippyModuleClasses = [NSMutableArray new]; }); HPAssert([moduleClass conformsToProtocol:@protocol(HippyBridgeModule)], @"%@ does not conform to the HippyBridgeModule protocol", moduleClass); // Register module [HippyModuleClasses addObject:moduleClass]; } /** * This function returns the module name for a given class. */ NSString *HippyBridgeModuleNameForClass(Class cls) { #if HP_DEBUG HPAssert([cls conformsToProtocol:@protocol(HippyBridgeModule)] || [cls conformsToProtocol:@protocol(HippyTurboModule)], @"Bridge module `%@` does not conform to HippyBridgeModule or HippyTurboModule", cls); #endif NSString *name = nil; // The two protocols(HippyBridgeModule and HippyTurboModule) should be mutually exclusive. if ([cls conformsToProtocol:@protocol(HippyBridgeModule)]) { name = [cls moduleName]; } else if ([cls conformsToProtocol:@protocol(HippyTurboModule)]) { name = [cls turoboModuleName]; } if (name.length == 0) { name = NSStringFromClass(cls); } if ([name hasPrefix:@"Hippy"] || [name hasPrefix:@"hippy"]) { if ([name isEqualToString:@"HippyIFrame"]) { } else { name = [name substringFromIndex:5]; } } return name; } #if HP_DEBUG void HippyVerifyAllModulesExported(NSArray *extraModules) { // Check for unexported modules unsigned int classCount; Class *classes = objc_copyClassList(&classCount); NSMutableSet *moduleClasses = [NSMutableSet new]; [moduleClasses addObjectsFromArray:HippyGetModuleClasses()]; [moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]]; for (unsigned int i = 0; i < classCount; i++) { Class cls = classes[i]; Class superclass = cls; while (superclass) { if (class_conformsToProtocol(superclass, @protocol(HippyBridgeModule))) { if ([moduleClasses containsObject:cls]) { break; } // Verify it's not a super-class of one of our moduleClasses BOOL isModuleSuperClass = NO; for (Class moduleClass in moduleClasses) { if ([moduleClass isSubclassOfClass:cls]) { isModuleSuperClass = YES; break; } } if (isModuleSuperClass) { break; } HPLogWarn(@"Class %@ was not exported. Did you forget to use HIPPY_EXPORT_MODULE()?", cls); break; } superclass = class_getSuperclass(superclass); } } free(classes); } #endif @interface HippyModulesSetup () { HippyBridgeModuleProviderBlock _providerBlock; __weak HippyBridge *_bridge; NSDictionary *_moduleDataByName; NSArray *_moduleDataByID; NSArray *_moduleClassesByID; } @property(readwrite, assign) BOOL moduleSetupComplete; @end @implementation HippyModulesSetup - (instancetype)initWithBridge:(HippyBridge *)bridge extraProviderModulesBlock:(HippyBridgeModuleProviderBlock)moduleProvider { self = [super init]; if (self) { _bridge = bridge; _providerBlock = [moduleProvider copy]; } return self; } - (void)invalidate { } - (void)dealloc { } - (HippyBridgeModuleProviderBlock)moduleProvider { return [_providerBlock copy]; } - (void)setupModulesCompletion:(dispatch_block_t)completion { HPLogInfo(@"Begin Modules Setup"); NSArray> *extraModules = _providerBlock ? _providerBlock() : @[]; #if HP_DEBUG static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ HippyVerifyAllModulesExported(extraModules); }); #endif //HP_DEBUG NSMutableArray *moduleClassesByID = [NSMutableArray new]; NSMutableArray *moduleDataByID = [NSMutableArray new]; NSMutableDictionary *moduleDataByName = [NSMutableDictionary new]; for (id module in extraModules) { Class moduleClass = [module class]; NSString *moduleName = HippyBridgeModuleNameForClass(moduleClass); if (HP_DEBUG) { // Check for name collisions between preregistered modules HippyModuleData *moduleData = moduleDataByName[moduleName]; if (moduleData) { HPLogError(@"Attempted to register HippyBridgeModule class %@ for the " "name '%@', but name was already registered by class %@", moduleClass, moduleName, moduleData.moduleClass); continue; } } // Instantiate moduleData container HippyModuleData *moduleData = [[HippyModuleData alloc] initWithModuleInstance:module bridge:_bridge]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } for (Class moduleClass in HippyGetModuleClasses()) { NSString *moduleName = HippyBridgeModuleNameForClass(moduleClass); // Check for module name collisions HippyModuleData *moduleData = moduleDataByName[moduleName]; if (moduleData) { if (moduleData.hasInstance) { // Existing module was preregistered, so it takes precedence continue; } else if ([moduleClass new] == nil) { // The new module returned nil from init, so use the old module continue; } else if ([moduleData.moduleClass new] != nil) { // Both modules were non-nil, so it's unclear which should take precedence HPLogError(@"Attempted to register HippyBridgeModule class %@ for the " "name '%@', but name was already registered by class %@", moduleClass, moduleName, moduleData.moduleClass); } } // Instantiate moduleData (TODO: can we defer this until config generation?) moduleData = [[HippyModuleData alloc] initWithModuleClass:moduleClass bridge:_bridge]; moduleDataByName[moduleName] = moduleData; [moduleClassesByID addObject:moduleClass]; [moduleDataByID addObject:moduleData]; } // Store modules _moduleDataByID = [moduleDataByID copy]; _moduleDataByName = [moduleDataByName copy]; _moduleClassesByID = [moduleClassesByID copy]; [self prepareModules]; self.moduleSetupComplete = YES; HPLogInfo(@"End Modules Setup"); if (completion) { completion(); } } - (void)prepareModules { HippyBridge *bridge = _bridge; if (![bridge isValid]) { return; } for (HippyModuleData *moduleData in _moduleDataByID) { if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) { // Modules that need to be set up on the main thread cannot be initialized // lazily when required without doing a dispatch_sync to the main thread, // which can result in deadlock. To avoid this, we initialize all of these // modules on the main thread in parallel with loading the JS code, so // they will already be available before they are ever required. dispatch_block_t block = ^{ (void)[moduleData instance]; [moduleData gatherConstants]; }; if (HPIsMainQueue()) { block(); } else { dispatch_async(dispatch_get_main_queue(), block); } } else { } } } - (NSDictionary *)moduleDataByName { return [_moduleDataByName copy]; } - (NSArray *)moduleDataByID { return [_moduleDataByID copy]; } - (NSArray *)moduleClasses { return [_moduleClassesByID copy]; } - (id)moduleForName:(NSString *)moduleName { HPAssert(moduleName, @"module name must not be null for [HippyModulesSetup moduleForName:]"); id module = _moduleDataByName[moduleName].instance; return module; } - (id)moduleForClass:(Class)cls { HPAssert(cls, @"class must not be null for [HippyModulesSetup moduleForClass:]"); return [self moduleForName:HippyBridgeModuleNameForClass(cls)]; } - (BOOL)moduleIsInitialized:(Class)moduleClass { HippyModuleData *module = _moduleDataByName[HippyBridgeModuleNameForClass(moduleClass)]; return module.hasInstance; } @end