mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-18 21:50:54 +08:00
285 lines
9.8 KiB
Plaintext
285 lines
9.8 KiB
Plaintext
/*!
|
|
* 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<Class> *HippyModuleClasses;
|
|
NSArray<Class> *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<NSString *, HippyModuleData *> *_moduleDataByName;
|
|
NSArray<HippyModuleData *> *_moduleDataByID;
|
|
NSArray<Class> *_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<id<HippyBridgeModule>> *extraModules = _providerBlock ? _providerBlock() : @[];
|
|
#if HP_DEBUG
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
HippyVerifyAllModulesExported(extraModules);
|
|
});
|
|
#endif //HP_DEBUG
|
|
NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
|
|
NSMutableArray<HippyModuleData *> *moduleDataByID = [NSMutableArray new];
|
|
NSMutableDictionary<NSString *, HippyModuleData *> *moduleDataByName = [NSMutableDictionary new];
|
|
|
|
for (id<HippyBridgeModule> 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<NSString *, HippyModuleData *> *)moduleDataByName {
|
|
return [_moduleDataByName copy];
|
|
}
|
|
|
|
- (NSArray<HippyModuleData *> *)moduleDataByID {
|
|
return [_moduleDataByID copy];
|
|
}
|
|
|
|
- (NSArray<Class> *)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
|