From a493f249b2066c711223f4a3ad648d02dbb23e87 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 26 Nov 2024 17:56:10 +0800 Subject: [PATCH] refactor(ios): code clean and structure optimization for HippyBridge --- .../TurboModuleDemo/TurboBaseModule.mm | 1 + .../ios/base/bridge/HippyBridge+BundleLoad.h | 52 ++ .../ios/base/bridge/HippyBridge+BundleLoad.mm | 283 +++++++ .../base/bridge/HippyBridge+ModuleManage.h | 77 ++ .../base/bridge/HippyBridge+ModuleManage.mm | 104 +++ .../base/bridge/HippyBridge+PerformanceAPI.h | 1 - .../base/bridge/HippyBridge+PerformanceAPI.mm | 24 +- .../ios/base/bridge/HippyBridge+Private.h | 21 +- framework/ios/base/bridge/HippyBridge.h | 130 ++-- framework/ios/base/bridge/HippyBridge.mm | 725 +++++------------- .../ios/base/executors/HippyJSExecutor.mm | 3 +- .../ios/base/modules/HippyEventDispatcher.mm | 1 + framework/ios/base/modules/HippyModuleData.mm | 4 +- framework/ios/module/dev/HippyDevMenu.mm | 2 +- framework/ios/module/dev/HippyRedBox.mm | 1 + .../ios/module/turbo/HippyOCTurboModule.mm | 4 +- modules/ios/base/HippyDefines.h | 5 + renderer/native/ios/renderer/HippyComponent.h | 2 +- renderer/native/ios/renderer/HippyRootView.mm | 1 + renderer/native/ios/renderer/HippyUIManager.h | 6 +- .../native/ios/renderer/HippyUIManager.mm | 4 +- tests/ios/HippyBridgeTest.mm | 85 +- tests/ios/HippyUIManagerTest.mm | 2 +- tests/ios/HippyUIViewCategoryTest.m | 2 +- 24 files changed, 918 insertions(+), 622 deletions(-) create mode 100644 framework/ios/base/bridge/HippyBridge+BundleLoad.h create mode 100644 framework/ios/base/bridge/HippyBridge+BundleLoad.mm create mode 100644 framework/ios/base/bridge/HippyBridge+ModuleManage.h create mode 100644 framework/ios/base/bridge/HippyBridge+ModuleManage.mm diff --git a/framework/examples/ios-demo/HippyDemo/TurboModuleDemo/TurboBaseModule.mm b/framework/examples/ios-demo/HippyDemo/TurboModuleDemo/TurboBaseModule.mm index c80cea29d9e..4fff421f107 100644 --- a/framework/examples/ios-demo/HippyDemo/TurboModuleDemo/TurboBaseModule.mm +++ b/framework/examples/ios-demo/HippyDemo/TurboModuleDemo/TurboBaseModule.mm @@ -25,6 +25,7 @@ #import "HippyBridgeModule.h" #import "TurboConfig.h" #import "HippyBridge.h" +#import "HippyBridge+ModuleManage.h" #import "HippyDefines.h" @implementation TurboBaseModule diff --git a/framework/ios/base/bridge/HippyBridge+BundleLoad.h b/framework/ios/base/bridge/HippyBridge+BundleLoad.h new file mode 100644 index 00000000000..5694cc8ebd5 --- /dev/null +++ b/framework/ios/base/bridge/HippyBridge+BundleLoad.h @@ -0,0 +1,52 @@ +/*! + * 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 "HippyBridge.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Category of HippyBridge responsible for loading bundle. +@interface HippyBridge (BundleLoad) + +typedef NSUInteger HippyBridgeBundleType; +typedef void (^HippyBridgeBundleLoadCompletionBlock)(NSURL * _Nullable bundleURL, NSError * _Nullable error); + +/// Setup bundle queue for bundle load operation. +- (void)prepareBundleQueue; + +/// Whether the bridge is loading bundle +@property (nonatomic, readonly, getter=isLoading) BOOL loading; + +/// Load and Execute bundle from the given bundle URL +/// - Parameters: +/// - bundleURL: bundle url +/// - bundleType: type of bundle, e.g.: whether is `Vendor Bundle`(Common Bundle) or `Business Bundle` +/// - completion: Completion block +/// +/// - Disscusion: HippyBridge makes sure bundles will be loaded and execute in order. +- (void)loadBundleURL:(NSURL *)bundleURL + bundleType:(HippyBridgeBundleType)bundleType + completion:(HippyBridgeBundleLoadCompletionBlock)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bridge/HippyBridge+BundleLoad.mm b/framework/ios/base/bridge/HippyBridge+BundleLoad.mm new file mode 100644 index 00000000000..3d797e6dc18 --- /dev/null +++ b/framework/ios/base/bridge/HippyBridge+BundleLoad.mm @@ -0,0 +1,283 @@ +/*! + * 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 "HippyBridge+BundleLoad.h" +#import "HippyBridge+Private.h" +#import "HippyBridge+VFSLoader.h" +#import "HippyJSExecutor.h" +#import "HippyAssert.h" +#import "HippyRedBox.h" +#import "HippyLog.h" + + +// Bundle related +#define HIPPY_BUNDLE_FETCH_TIMEOUT_SEC 30 // Bundle fetch operation timeout value, 30s +static NSString *const kHippyBundleFetchQueueName = @"com.hippy.bundleQueue.fetch"; +static NSString *const kHippyBundleExecuteQueueName = @"com.hippy.bundleQueue.execute"; +static NSString *const kHippyBundleLoadErrorDomain = @"HippyBundleLoadErrorDomain"; +static NSString *const kFileUriScheme = @"file"; + + +@implementation HippyBridge (BundleLoad) + +- (void)prepareBundleQueue { + NSOperationQueue *bundleQueue = [[NSOperationQueue alloc] init]; + bundleQueue.qualityOfService = NSQualityOfServiceUserInitiated; + bundleQueue.name = kHippyBundleFetchQueueName; + bundleQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; + self.bundleQueue = bundleQueue; +} + +- (BOOL)isLoading { + HippyAssertMainQueue(); + return self.loadingCount > 0; +} + +#pragma mark - Bundle Load + +#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(whichSelf) \ +@{ kHippyNotiBridgeKey: whichSelf, \ + kHippyNotiBundleUrlKey: bundleURL, \ + kHippyNotiBundleTypeKey : @(bundleType) } + +#define BUNDLE_LOAD_NOTI_ERROR_USER_INFO(whichSelf) \ +@{ kHippyNotiBridgeKey: whichSelf, \ + kHippyNotiBundleUrlKey: bundleURL, \ + kHippyNotiBundleTypeKey : @(bundleType), \ + kHippyNotiErrorKey : error } + +- (void)loadBundleURL:(NSURL *)bundleURL + bundleType:(HippyBridgeBundleType)bundleType + completion:(nonnull HippyBridgeBundleLoadCompletionBlock)completion { + HippyAssertParam(bundleURL); + if (!bundleURL) { + if (completion) { + static NSString *bundleError = @"bundle url is nil"; + NSError *error = HippyErrorWithMessage(bundleError); + completion(nil, error); + } + return; + } + + // bundleURL checking + NSURLComponents *components = [NSURLComponents componentsWithURL:bundleURL resolvingAgainstBaseURL:NO]; + if (components.scheme == nil) { + // If a given url has no scheme, it is considered a file url by default. + components.scheme = kFileUriScheme; + bundleURL = components.URL; + } + + HippyLogInfo(@"[HP PERF] Begin loading bundle(%s) at %s", + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String), + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.UTF8String)); + [self.allBundleURLs addObject:bundleURL]; + + NSDictionary *userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(self); + [[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScriptWillStartLoadingNotification + object:self + userInfo:userInfo]; + [self beginLoadingBundle:bundleURL bundleType:bundleType completion:completion]; +} + +- (void)beginLoadingBundle:(NSURL *)bundleURL + bundleType:(HippyBridgeBundleType)bundleType + completion:(HippyBridgeBundleLoadCompletionBlock)completion { + HippyAssertMainQueue(); + HippyAssertParam(bundleURL); + HippyAssertParam(completion); + + __weak __typeof(self)weakSelf = self; + __block NSData *script = nil; + self.loadingCount++; + + // Fetch operation + NSBlockOperation *fetchOperation = [NSBlockOperation blockOperationWithBlock:^{ + __strong __typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + HippyLogInfo(@"Start fetching bundle(%s)", + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); + // create semaphore + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [strongSelf fetchBundleWithURL:bundleURL completion:^(NSData *source, NSError *error) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if (!strongSelf || !bundleURL) { + return; + } + NSDictionary *userInfo; + if (error) { + HippyBridgeFatal(error, strongSelf); + userInfo = BUNDLE_LOAD_NOTI_ERROR_USER_INFO(strongSelf); + } else { + script = source; + userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(strongSelf); + } + [[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScripDidLoadSourceCodeNotification + object:strongSelf + userInfo:userInfo]; + HippyLogInfo(@"End fetching bundle(%s) error?:%@", + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String), error); + dispatch_semaphore_signal(semaphore); // release semaphore + }]; + // wait semaphore + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, HIPPY_BUNDLE_FETCH_TIMEOUT_SEC * NSEC_PER_SEC); + intptr_t result = dispatch_semaphore_wait(semaphore, timeout); + if (result != 0) { + HippyLogError(@"Fetch operation timed out!!! (30s)"); + } + }]; + + // Execution operation + NSBlockOperation *executeOperation = [NSBlockOperation blockOperationWithBlock:^{ + HippyLogInfo(@"Start executing bundle(%s)", + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); + __strong __typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid || !script) { + NSString *errMsg = [NSString stringWithFormat:@"Bundle Execution Operation Fail! valid:%d, script:%@", + strongSelf.valid, script]; + HippyLogError(@"%@", errMsg); + completion(bundleURL, HippyErrorWithMessage(errMsg)); + @synchronized (self) { + strongSelf.lastExecuteOperation = nil; + } + return; + } + [strongSelf executeJSCode:script sourceURL:bundleURL onCompletion:^(id result, NSError *error) { + HippyLogInfo(@"End executing bundle(%s)", + HIPPY_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); + @synchronized (self) { + strongSelf.lastExecuteOperation = nil; + } + if (completion) { + completion(bundleURL, error); + } + if (!strongSelf || !strongSelf.valid) { + return; + } + if (error) { + HippyBridgeFatal(error, strongSelf); + } + + dispatch_async(dispatch_get_main_queue(), ^{ + __strong __typeof(weakSelf)strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf.loadingCount--; + NSNotificationName notiName = error ? HippyJavaScriptDidFailToLoadNotification : HippyJavaScriptDidLoadNotification; + NSDictionary *userInfo = error ? BUNDLE_LOAD_NOTI_ERROR_USER_INFO(strongSelf) : BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(strongSelf); + [[NSNotificationCenter defaultCenter] postNotificationName:notiName object:strongSelf userInfo:userInfo]; + }); + }]; + }]; + + // Add dependency, make sure that doing fetch before execute, + // and all execution operations must be queued. + [executeOperation addDependency:fetchOperation]; + @synchronized (self) { + NSOperation *lastOp = self.lastExecuteOperation; + if (lastOp) { + [executeOperation addDependency:lastOp]; + } + } + + // Enqueue operation + [self.bundleQueue addOperations:@[fetchOperation, executeOperation] waitUntilFinished:NO]; + @synchronized (self) { + self.lastExecuteOperation = executeOperation; + } +} + +#pragma mark - Bundle Fetch and Execute + +/// Fetch JS Bundle +- (void)fetchBundleWithURL:(NSURL *)bundleURL completion:(void (^)(NSData *source, NSError *error))completion { + HippyAssertParam(bundleURL); + HippyAssertParam(completion); + // Fetch the bundle + // Call the completion handler with the fetched data or error + [self loadContentsAsynchronouslyFromUrl:bundleURL.absoluteString + method:@"get" + params:nil + body:nil + queue:nil + progress:nil + completionHandler:^(NSData * _Nullable data, + NSDictionary * _Nullable userInfo, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + completion(data, error); + }]; +} + +/// Execute JS Bundle +- (void)executeJSCode:(NSData *)script + sourceURL:(NSURL *)sourceURL + onCompletion:(HippyJavaScriptCallback)completion { + if (!script) { + completion(nil, HippyErrorWithMessageAndModuleName(@"no valid data", self.moduleName)); + return; + } + if (![self isValid] || !script || !sourceURL) { + completion(nil, HippyErrorWithMessageAndModuleName(@"bridge is not valid", self.moduleName)); + return; + } + HippyAssert(self.javaScriptExecutor, @"js executor must not be null"); + __weak __typeof(self)weakSelf = self; + [self.javaScriptExecutor executeApplicationScript:script sourceURL:sourceURL onComplete:^(id result ,NSError *error) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if (!strongSelf || ![strongSelf isValid]) { + completion(result, error); + return; + } + if (error) { + HippyLogError(@"ExecuteApplicationScript Error! %@", error.description); + HippyExecuteOnMainQueue(^{ + __strong __typeof(weakSelf)strongSelf = weakSelf; + [strongSelf stopLoadingWithError:error scriptSourceURL:sourceURL]; + }); + } + completion(result, error); + }]; +} + +- (void)stopLoadingWithError:(NSError *)error scriptSourceURL:(NSURL *)sourceURL { + HippyAssertMainQueue(); + if (![self isValid]) { + return; + } + __weak HippyBridge *weakSelf = self; + [self.javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + @autoreleasepool { + HippyBridge *strongSelf = weakSelf; + if (!strongSelf || ![strongSelf isValid]) { + [strongSelf.javaScriptExecutor invalidate]; + } + } + }]; + if ([error userInfo][HippyJSStackTraceKey]) { + [self.redBox showErrorMessage:[error localizedDescription] withStack:[error userInfo][HippyJSStackTraceKey]]; + } +} + +@end diff --git a/framework/ios/base/bridge/HippyBridge+ModuleManage.h b/framework/ios/base/bridge/HippyBridge+ModuleManage.h new file mode 100644 index 00000000000..86e51729a67 --- /dev/null +++ b/framework/ios/base/bridge/HippyBridge+ModuleManage.h @@ -0,0 +1,77 @@ +/*! + * 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 "HippyBridge.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Module Management Category of HippyBridge +@interface HippyBridge (ModuleManage) + +/// Get all native module info. +- (NSDictionary *)nativeModuleConfig; + +/// Get config info for given module name. +/// - Parameter moduleName: name of module +- (NSArray *)configForModuleName:(NSString *)moduleName; + +/// Whether is module setup complete. +- (BOOL)isModuleSetupComplete; + +/// Retrieve a bridge module instance by name. Note that modules are lazily instantiated, +/// so calling these methods for the first time with a given +/// module name/class may cause the class to be sychronously instantiated, +/// potentially blocking both the calling thread and main thread for a short time. +/// - Parameter moduleName: name of module +- (nullable id)moduleForName:(NSString *)moduleName; + +/// Retrieve a bridge module instance by class. +/// see `moduleForName` for more. +/// - Parameter moduleClass: class of module +- (nullable id)moduleForClass:(Class)moduleClass; + +/// Get ModuleData by name. +/// - Parameter moduleName: JS name of module +- (nullable HippyModuleData *)moduleDataForName:(NSString *)moduleName; + +/** + * Convenience method for retrieving all modules conforming to a given protocol. + * Modules will be sychronously instantiated if they haven't already been, + * potentially blocking both the calling thread and main thread for a short time. + */ +- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol; + +/** + * Test if a module has been initialized. Use this prior to calling + * `moduleForClass:` or `moduleForName:` if you do not want to cause the module + * to be instantiated if it hasn't been already. + */ +- (BOOL)moduleIsInitialized:(Class)moduleClass; + +/// Get turbo module by name. +/// - Parameter name: name of turbo module +- (id)turboModuleWithName:(NSString *)name; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bridge/HippyBridge+ModuleManage.mm b/framework/ios/base/bridge/HippyBridge+ModuleManage.mm new file mode 100644 index 00000000000..126132ba1d1 --- /dev/null +++ b/framework/ios/base/bridge/HippyBridge+ModuleManage.mm @@ -0,0 +1,104 @@ +/*! + * 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 "HippyBridge+ModuleManage.h" +#import "HippyBridge+Private.h" +#import "HippyModuleData.h" +#import "HippyOCTurboModule.h" +#import "HippyTurboModuleManager.h" +#import "HippyUtils.h" + + +// Key of module config info for js side +static NSString *const kHippyRemoteModuleConfigKey = @"remoteModuleConfig"; + + +@implementation HippyBridge (ModuleManage) + +#pragma mark - Module Management + +- (NSArray *)moduleClasses { + return self.moduleSetup.moduleClasses; +} + +- (id)moduleForName:(NSString *)moduleName { + return [self.moduleSetup moduleForName:moduleName]; +} + +- (id)moduleForClass:(Class)moduleClass { + return [self.moduleSetup moduleForClass:moduleClass]; +} + +- (HippyModuleData *)moduleDataForName:(NSString *)moduleName { + if (moduleName) { + return self.moduleSetup.moduleDataByName[moduleName]; + } + return nil; +} + +- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol { + NSMutableArray *modules = [NSMutableArray new]; + for (Class moduleClass in self.moduleClasses) { + if ([moduleClass conformsToProtocol:protocol]) { + id module = [self moduleForClass:moduleClass]; + if (module) { + [modules addObject:module]; + } + } + } + return [modules copy]; +} + +- (BOOL)moduleIsInitialized:(Class)moduleClass { + return [self.moduleSetup isModuleInitialized:moduleClass]; +} + +- (BOOL)isModuleSetupComplete { + return self.moduleSetup.isModuleSetupComplete; +} + +- (NSDictionary *)nativeModuleConfig { + NSMutableArray *config = [NSMutableArray new]; + for (HippyModuleData *moduleData in [self.moduleSetup moduleDataByID]) { + NSArray *moduleDataConfig = [moduleData config]; + [config addObject:HippyNullIfNil(moduleDataConfig)]; + } + return @{ kHippyRemoteModuleConfigKey : config }; +} + +- (NSArray *)configForModuleName:(NSString *)moduleName { + HippyModuleData *moduleData = [self.moduleSetup moduleDataByName][moduleName]; + return moduleData.config; +} + +- (HippyOCTurboModule *)turboModuleWithName:(NSString *)name { + if (!self.enableTurbo || name.length <= 0) { + return nil; + } + + if (!self.turboModuleManager) { + self.turboModuleManager = [[HippyTurboModuleManager alloc] initWithBridge:self]; + } + return [self.turboModuleManager turboModuleWithName:name]; +} + +@end diff --git a/framework/ios/base/bridge/HippyBridge+PerformanceAPI.h b/framework/ios/base/bridge/HippyBridge+PerformanceAPI.h index 739e8e09242..fff8a87ba30 100644 --- a/framework/ios/base/bridge/HippyBridge+PerformanceAPI.h +++ b/framework/ios/base/bridge/HippyBridge+PerformanceAPI.h @@ -21,7 +21,6 @@ */ #import "HippyBridge.h" -#import "HippyBridge+PerformanceAPI.h" NS_ASSUME_NONNULL_BEGIN diff --git a/framework/ios/base/bridge/HippyBridge+PerformanceAPI.mm b/framework/ios/base/bridge/HippyBridge+PerformanceAPI.mm index e968f33fb8d..32189a6188f 100644 --- a/framework/ios/base/bridge/HippyBridge+PerformanceAPI.mm +++ b/framework/ios/base/bridge/HippyBridge+PerformanceAPI.mm @@ -26,6 +26,16 @@ #import "driver/scope.h" #import "footstone/string_view_utils.h" + +static NSString *const kHippyPerfKeyFP = @"FP"; +static NSString *const kHippyPerfKeyFCP = @"FCP"; +static NSString *const kHippyPerfKeyInit = @"NativeInit"; +static NSString *const kHippyPerfKeyJSInit = @"JsEngineInit"; +static NSString *const kHippyPerfKeyRunApp = @"RunApplication"; +static NSString *const kHippyPerfKeyDomCreate = @"DomCreate"; +static NSString *const kHippyPerfKeyFirstFrame = @"FirstFrame"; + + using namespace footstone; @implementation HippyBridge (PerformanceAPI) @@ -96,12 +106,12 @@ - (NSDictionary *)getHippyInitPerformanceData { int64_t runApplication = (entry->GetHippyRunApplicationEnd() - entry->GetHippyRunApplicationStart()).ToMilliseconds(); int64_t domCreate = (entry->GetHippyDomEnd() - entry->GetHippyDomStart()).ToMilliseconds(); int64_t firstFrame = (entry->GetHippyFirstFrameEnd() - entry->GetHippyFirstFrameStart()).ToMilliseconds(); - dic[@"0.FP"] = @(totalFPTime); - dic[@"1.NativeInit"] = @(nativeInit); - dic[@"2.JsEngineInit"] = @(jsEngineInit); - dic[@"3.RunApplication"] = @(runApplication); - dic[@"4.DomCreate"] = @(domCreate); - dic[@"5.FirstFrame"] = @(firstFrame); + dic[kHippyPerfKeyFP] = @(totalFPTime); + dic[kHippyPerfKeyInit] = @(nativeInit); + dic[kHippyPerfKeyJSInit] = @(jsEngineInit); + dic[kHippyPerfKeyRunApp] = @(runApplication); + dic[kHippyPerfKeyDomCreate] = @(domCreate); + dic[kHippyPerfKeyFirstFrame] = @(firstFrame); auto bundle_info_array = entry->GetBundleInfoArray(); for (size_t i = 0; i < bundle_info_array.size(); ++i) { @@ -129,7 +139,7 @@ - (NSDictionary *)getFCPPerformanceData { return nil; } int64_t fcpTime = (entry->GetHippyFirstContentfulPaintEnd() - entry->GetHippyNativeInitStart()).ToMilliseconds(); - return @{ @"FCP" : @(fcpTime) }; + return @{ kHippyPerfKeyFCP : @(fcpTime) }; } return nil; } diff --git a/framework/ios/base/bridge/HippyBridge+Private.h b/framework/ios/base/bridge/HippyBridge+Private.h index 2a333975c9c..8f895a89967 100644 --- a/framework/ios/base/bridge/HippyBridge+Private.h +++ b/framework/ios/base/bridge/HippyBridge+Private.h @@ -24,6 +24,7 @@ #define HippyBridge_Private_h #import "HippyBridge.h" +#import "HippyModulesSetup.h" #include "footstone/time_point.h" #include @@ -37,6 +38,8 @@ class RootNode; }; +NS_ASSUME_NONNULL_BEGIN + @protocol HippyBridgeInternal /// URI Loader @@ -45,6 +48,22 @@ class RootNode; /// Start time of hippyBridge, for performance api. @property (nonatomic, assign) footstone::TimePoint startTime; +/// Helper class responsible for managing Modules +@property (nonatomic, strong) HippyModulesSetup *moduleSetup; + +/// Bundle loading count, +/// used to indicate whether is in loading state. +@property (nonatomic, assign) NSInteger loadingCount; + +/// Urls of all js bundles +@property (nonatomic, strong) NSMutableArray *allBundleURLs; + +/// Bundle fetch operation queue (concurrent) +@property (nonatomic, strong) NSOperationQueue *bundleQueue; + +/// Record the last execute operation for adding execution dependency. +@property (nonatomic, strong, nullable) NSOperation *lastExecuteOperation; + @end @@ -60,6 +79,6 @@ class RootNode; @end - +NS_ASSUME_NONNULL_END #endif /* HippyBridge_Private_h */ diff --git a/framework/ios/base/bridge/HippyBridge.h b/framework/ios/base/bridge/HippyBridge.h index b21b9767ca8..c4151c1b55a 100644 --- a/framework/ios/base/bridge/HippyBridge.h +++ b/framework/ios/base/bridge/HippyBridge.h @@ -39,23 +39,33 @@ NS_ASSUME_NONNULL_BEGIN /** * Indicate hippy sdk version - * 注意:为兼容2.0版本,保持的相同的下划线前缀命名,不可修改 + * Note: To be compatible with version 2.0, + * the same underscore prefix is ​​used and cannot be modified. */ HIPPY_EXTERN NSString *const _HippySDKVersion; -/** - * This notification triggers a reload of all bridges currently running. - * Deprecated, use HippyBridge::requestReload instead. - */ -HIPPY_EXTERN NSString *const HippyReloadNotification; +/// Launch Options Key: DebugMode +/// Set to YES will automatically start debugger. +/// Default is NO. +HIPPY_EXTERN NSString *const kHippyLaunchOptionsDebugModeKey; + +/// Launch Options Key: EnableTurbo +/// Set to YES will enable jsi mode. +/// Default is YES. +HIPPY_EXTERN NSString *const kHippyLaunchOptionsEnableTurboKey; // Keys of userInfo for the following notifications +/// key of bridge in userInfo HIPPY_EXTERN NSString *const kHippyNotiBridgeKey; +/// url key in userInfo HIPPY_EXTERN NSString *const kHippyNotiBundleUrlKey; +/// bundle type key in userInfo HIPPY_EXTERN NSString *const kHippyNotiBundleTypeKey; +/// error key in userInfo HIPPY_EXTERN NSString *const kHippyNotiErrorKey; + /// Bundle Type of Vendor (or Common Bundle), /// used in kHippyNotiBundleTypeKey HIPPY_EXTERN const NSUInteger HippyBridgeBundleTypeVendor; @@ -74,8 +84,9 @@ HIPPY_EXTERN const NSUInteger HippyBridgeBundleTypeBusiness; * kHippyNotiBundleTypeKey : $(bundleType), * } * - * 备注:bundle包开始加载的通知, 注意与Hippy2不同的是,不仅指代`Common包`,`Business包`同样会发送该通知, - * 可通过userInfo中bundleType参数进行区分,see: HippyBridgeBundleTypeVendor + * Note: Notification of bundle loading. + * Note that unlike Hippy2, this notification is sent not only for `Common package`, but also for `Business package`. + * It can be distinguished by the bundleType parameter in userInfo, see: HippyBridgeBundleTypeVendor for more. */ HIPPY_EXTERN NSString *const HippyJavaScriptWillStartLoadingNotification; @@ -91,12 +102,12 @@ HIPPY_EXTERN NSString *const HippyJavaScriptWillStartLoadingNotification; * kHippyNotiErrorKey : $(error), // NSError object * } * - * 备注:获取到Bundle包的source code data时的通知 + * Note: Notification when the source code data of the Bundle package is obtained. */ HIPPY_EXTERN NSString *const HippyJavaScripDidLoadSourceCodeNotification; /** - * This notification fires when the bridge has finished loading the JS bundle. + * This notification fires when bridge has finished loading JS bundle. * @discussion * Notification.object: instance of HippyBridge * Notification.userInfo: @@ -106,7 +117,7 @@ HIPPY_EXTERN NSString *const HippyJavaScripDidLoadSourceCodeNotification; * kHippyNotiBundleTypeKey : $(bundleType), * } * - * 备注:Bundle包`加载和执行`结束的通知 + * Note: Notification of the end of Bundle `loading and execution` */ HIPPY_EXTERN NSString *const HippyJavaScriptDidLoadNotification; @@ -123,7 +134,7 @@ HIPPY_EXTERN NSString *const HippyJavaScriptDidLoadNotification; * kHippyNotiErrorKey : $(error), // NSError object * } * - * 备注:Bundle包`加载和执行`失败的通知 + * Note: Notification of Bundle package `loading and execution` failure */ HIPPY_EXTERN NSString *const HippyJavaScriptDidFailToLoadNotification; @@ -135,13 +146,17 @@ HIPPY_EXTERN NSString *const HippyJavaScriptDidFailToLoadNotification; */ HIPPY_EXTERN NSString *const HippyDidInitializeModuleNotification; +/** + * This notification is sent when hippy bridge is reloaded. + */ +HIPPY_EXTERN NSString *const HippyReloadNotification; + /** * This function returns the module name for a given class. */ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); - #pragma mark - /// Async bridge used to communicate with the JavaScript application. @@ -154,8 +169,10 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); /// @param launchOptions launch options, will not be sent to frontend /// @param executorKey key to engine instance. HippyBridge with same engine key will share same engine intance. /// -/// Note: 多个bridge使用相同的共享engineKey时,只有全部bridge实例销毁时engine资源才将释放,因此,请注意合理使用,避免出现意外的内存泄漏。 -/// 传空时默认不共享,SDK内部默认分配一随机key。 +/// Note: When multiple bridges use the same shared engineKey, +/// the engine resources will be released only when all bridge instances are destroyed. +/// Therefore, please use it properly to avoid unexpected memory leaks. +/// When executorKey is empty, it is not shared by default. A random key is assigned by default in the SDK. - (instancetype)initWithDelegate:(nullable id)delegate moduleProvider:(nullable HippyBridgeModuleProviderBlock)block launchOptions:(nullable NSDictionary *)launchOptions @@ -172,13 +189,19 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); /// @param launchOptions launch options, will not be sent to frontend /// @param executorKey key to engine instance. HippyBridge with same engine key will share same engine intance. /// -/// Note: 多个bridge使用相同的共享engineKey时,只有全部bridge实例销毁时engine资源才将释放,因此,请注意合理使用,避免出现意外的内存泄漏。 -/// 传空时默认不共享,SDK内部默认分配一随机key。 +/// Note: When multiple bridges use the same shared engineKey, +/// the engine resources will be released only when all bridge instances are destroyed. +/// Therefore, please use it properly to avoid unexpected memory leaks. +/// When executorKey is empty, it is not shared by default. A random key is assigned by default in the SDK. - (instancetype)initWithDelegate:(nullable id)delegate bundleURL:(nullable NSURL *)bundleURL moduleProvider:(nullable HippyBridgeModuleProviderBlock)block launchOptions:(nullable NSDictionary *)launchOptions - executorKey:(nullable NSString *)executorKey; + executorKey:(nullable NSString *)executorKey NS_DESIGNATED_INITIALIZER; + +// Not available +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; /// The delegate of bridge @property (nonatomic, weak, readonly) id delegate; @@ -207,9 +230,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); /// Reason for bridge invalidate state @property (nonatomic, assign) HippyInvalidateReason invalidateReason; -/// Whether the bridge is loading bundle -@property (nonatomic, readonly, getter=isLoading) BOOL loading; - /// All loaded bundle urls @property (nonatomic, copy, readonly) NSArray *bundleURLs; @@ -226,6 +246,7 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); /// Get Device Info - (NSDictionary *)deviceInfo; + #pragma mark - Image Related /// Get the custom Image Loader @@ -318,45 +339,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); /// All registered bridge module classes. @property (nonatomic, copy, readonly) NSArray *moduleClasses; -/// Get all native module info. -- (NSDictionary *)nativeModuleConfig; - -/// Get config info for given module name -/// - Parameter moduleName: name of module -- (NSArray *)configForModuleName:(NSString *)moduleName; - -- (BOOL)moduleSetupComplete; -/** - * Retrieve a bridge module instance by name or class. Note that modules are - * lazily instantiated, so calling these methods for the first time with a given - * module name/class may cause the class to be sychronously instantiated, - * potentially blocking both the calling thread and main thread for a short time. - */ -- (id)moduleForName:(NSString *)moduleName; -- (id)moduleForClass:(Class)moduleClass; - -/// Get ModuleData by name -/// - Parameter moduleName: JS name of module -- (nullable HippyModuleData *)moduleDataForName:(NSString *)moduleName; - -/** - * Convenience method for retrieving all modules conforming to a given protocol. - * Modules will be sychronously instantiated if they haven't already been, - * potentially blocking both the calling thread and main thread for a short time. - */ -- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol; - -/** - * Test if a module has been initialized. Use this prior to calling - * `moduleForClass:` or `moduleForName:` if you do not want to cause the module - * to be instantiated if it hasn't been already. - */ -- (BOOL)moduleIsInitialized:(Class)moduleClass; - -/// Get turbo module by name. -/// - Parameter name: name of turbo module -- (id)turboModuleWithName:(NSString *)name; - #pragma mark - Snapshot @@ -405,35 +387,13 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); #pragma mark - Advanced Usages -/* 说明: - * 以下方法一般情况下无需调用,仅供高级定制化使用。 - * Following methods are only used for advanced customization, no need to be invoked in general. - */ - /// Interceptor for methods @property (nonatomic, weak) id methodInterceptor; - -typedef NSUInteger HippyBridgeBundleType; -typedef void (^HippyBridgeBundleLoadCompletionBlock)(NSURL * _Nullable bundleURL, NSError * _Nullable error); - -/// Load and Execute bundle from the given bundle URL -/// - Parameters: -/// - bundleURL: bundle url -/// - bundleType: type of bundle, e.g.: whether is `Vendor Bundle`(Common Bundle) or `Business Bundle` -/// - completion: Completion block -/// -/// - Disscusion: HippyBridge makes sure bundles will be loaded and execute in order. -- (void)loadBundleURL:(NSURL *)bundleURL - bundleType:(HippyBridgeBundleType)bundleType - completion:(HippyBridgeBundleLoadCompletionBlock)completion; - - @end - -HIPPY_EXTERN void HippyBridgeFatal(NSError *, HippyBridge *); - -HIPPY_EXTERN void HippyBridgeHandleException(NSException *exception, HippyBridge *bridge); +/// Same as `HippyFatal`, with moduleName in userinfo. +/// see `HippyFatalModuleName` for more. +HIPPY_EXTERN void HippyBridgeFatal(NSError *error, HippyBridge *bridge); NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index 79e2748bc5d..845be2ddcdb 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -22,6 +22,8 @@ #import "HippyBridge.h" #import "HippyBridge+Private.h" +#import "HippyBridge+BundleLoad.h" +#import "HippyBridge+ModuleManage.h" #import "HippyDeviceBaseInfo.h" #import "HippyDisplayLink.h" #import "HippyEventDispatcher.h" @@ -30,9 +32,7 @@ #import "HippyJSExecutor+Internal.h" #import "HippyKeyCommands.h" #import "HippyModuleData.h" -#import "HippyModuleMethod.h" -#import "HippyTurboModuleManager.h" -#import "HippyOCTurboModule.h" +#import "HippyBridgeMethod.h" #import "HippyRedBox.h" #import "HippyTurboModule.h" #import "HippyUtils.h" @@ -46,7 +46,6 @@ #import "HippyUtils.h" #import "TypeConverter.h" #import "VFSUriLoader.h" -#import "HippyBridge+VFSLoader.h" #import "HippyBase64DataHandler.h" #import "NativeRenderManager.h" #import "HippyRootView.h" @@ -76,6 +75,7 @@ #endif +// Notifications related NSString *const _HippySDKVersion = @HIPPY_STR(HIPPY_VERSION); NSString *const HippyReloadNotification = @"HippyReloadNotification"; NSString *const HippyJavaScriptWillStartLoadingNotification = @"HippyJavaScriptWillStartLoadingNotification"; @@ -84,15 +84,19 @@ NSString *const HippyJavaScriptDidFailToLoadNotification = @"HippyJavaScriptDidFailToLoadNotification"; NSString *const HippyDidInitializeModuleNotification = @"HippyDidInitializeModuleNotification"; +// Notifications userinfo related NSString *const kHippyNotiBridgeKey = @"bridge"; NSString *const kHippyNotiBundleUrlKey = @"bundleURL"; NSString *const kHippyNotiBundleTypeKey = @"bundleType"; NSString *const kHippyNotiErrorKey = @"error"; - const NSUInteger HippyBridgeBundleTypeVendor = 1; const NSUInteger HippyBridgeBundleTypeBusiness = 2; +// Launch options keys +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"; @@ -102,14 +106,20 @@ static NSString *const HippyNativeGlobalKeyLocalization = @"Localization"; static NSString *const HippyNativeGlobalKeyNightMode = @"NightMode"; -// key of module config info for js side +// Key of module config info for js side static NSString *const kHippyRemoteModuleConfigKey = @"remoteModuleConfig"; static NSString *const kHippyBatchedBridgeConfigKey = @"__hpBatchedBridgeConfig"; +// Define constants for the URI handlers +static NSString *const kFileUriScheme = @"file"; +static NSString *const kHpFileUriScheme = @"hpfile"; +static NSString *const kDataUriScheme = @"data"; -#define HIPPY_BUNDLE_FETCH_TIMEOUT_SEC 30 // Bundle fetch operation timeout value, 30s -static NSString *const kHippyBundleFetchQueueName = @"com.hippy.bundleQueue.fetch"; -static NSString *const kHippyBundleExecuteQueueName = @"com.hippy.bundleQueue.execute"; +// Load and Unload instance param keys +static NSString *const kHippyLoadInstanceNameKey = @"name"; +static NSString *const kHippyLoadInstanceIdKey = @"id"; +static NSString *const kHippyLoadInstanceParamsKey = @"params"; +static NSString *const kHippyLoadInstanceVersionKey = @"version"; typedef NS_ENUM(NSUInteger, HippyBridgeFields) { HippyBridgeFieldRequestModuleIDs = 0, @@ -118,99 +128,69 @@ typedef NS_ENUM(NSUInteger, HippyBridgeFields) { HippyBridgeFieldCallID, }; -/// Set the log delegate for hippy core module -static inline void registerLogDelegateToHippyCore() { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - footstone::LogMessage::InitializeDelegate([](const std::ostringstream& stream, footstone::LogSeverity severity) { - HippyLogLevel logLevel = HippyLogLevelInfo; - - switch (severity) { - case footstone::TDF_LOG_INFO: - logLevel = HippyLogLevelInfo; - break; - case footstone::TDF_LOG_WARNING: - logLevel = HippyLogLevelWarning; - break; - case footstone::TDF_LOG_ERROR: - logLevel = HippyLogLevelError; - break; - case footstone::TDF_LOG_FATAL: - logLevel = HippyLogLevelFatal; - break; - default: - break; - } - HippyLogNativeInternal(logLevel, "tdf", 0, @"%s", stream.str().c_str()); - }); - }); -} - -@interface HippyBridge() { - __weak id _methodInterceptor; - HippyModulesSetup *_moduleSetup; +@interface HippyBridge () { + // Identifies whether batch updates are in progress. BOOL _wasBatchActive; + + // DisplayLink HippyDisplayLink *_displayLink; + + // Block used to get external injection modules HippyBridgeModuleProviderBlock _moduleProvider; - BOOL _valid; - NSMutableArray *_bundleURLs; + // VFSUriLoader instance std::shared_ptr _uriLoader; + + // hippy::RootNode instance std::shared_ptr _rootNode; // The C++ version of RenderManager instance, bridge holds, // One NativeRenderManager holds multiple UIManager instance. std::shared_ptr _renderManager; - // 缓存的设备信息 + // Cached device information, access only in single thread. NSDictionary *_cachedDeviceInfo; } -/// 用于标记bridge所使用的JS引擎的Key +/// The Key used to mark the JS engine used by the bridge /// -/// 注意:传入相同值的bridge将共享底层JS引擎。 -/// 在共享情况下,只有全部bridge实例均释放,JS引擎资源才会销毁。 -/// 默认情况下对每个bridge使用独立JS引擎 +/// Note: Bridges passing the same value will share the underlying JS engine. +/// In a shared case, JS engine resources are destroyed only when all bridge instances are released. +/// A separate JS engine is used for each bridge by default. @property (nonatomic, strong) NSString *engineKey; + /// Module setup semaphore -@property (readwrite, strong) dispatch_semaphore_t moduleSemaphore; +@property (nonatomic, strong) dispatch_semaphore_t moduleSemaphore; /// Pending load bundle's URL @property (nonatomic, strong) NSURL *pendingLoadingVendorBundleURL; -/// Bundle loading count, used to indicate whether is in loading state. -@property (nonatomic, assign) NSInteger loadingCount; -/// Bundle fetch operation queue (concurrent) -@property (nonatomic, strong) NSOperationQueue *bundleQueue; -/// Record the last execute operation for adding execution dependency. -@property (nonatomic, strong, nullable) NSOperation *lastExecuteOperation; /// Cached Dimensions info,will be passed to JS Side. @property (atomic, strong) NSDictionary *cachedDimensionsInfo; @end + @implementation HippyBridge @synthesize sandboxDirectory = _sandboxDirectory; @synthesize imageLoader = _imageLoader; @synthesize imageProviders = _imageProviders; @synthesize startTime = _startTime; - -dispatch_queue_t HippyJSThread; - -+ (void)initialize { - [super initialize]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Set up JS thread - HippyJSThread = (id)kCFNull; - }); -} - -- (instancetype)initWithDelegate:(id)delegate - moduleProvider:(HippyBridgeModuleProviderBlock)block - launchOptions:(NSDictionary *)launchOptions +@synthesize moduleSetup = _moduleSetup; +@synthesize allBundleURLs = _allBundleURLs; +@synthesize bundleQueue = _bundleQueue; +@synthesize loadingCount = _loadingCount; +@synthesize lastExecuteOperation = _lastExecuteOperation; + +// Use kCFNull to identify the use of JS thread, +// Reserve it for compatibility with hippy2. +dispatch_queue_t HippyJSThread = (id)kCFNull; + +- (instancetype)initWithDelegate:(nullable id)delegate + moduleProvider:(nullable HippyBridgeModuleProviderBlock)block + launchOptions:(nullable NSDictionary *)launchOptions executorKey:(nullable NSString *)executorKey { return [self initWithDelegate:delegate bundleURL:nil @@ -219,32 +199,28 @@ - (instancetype)initWithDelegate:(id)delegate executorKey:executorKey]; } -- (instancetype)initWithDelegate:(id)delegate - bundleURL:(NSURL *)bundleURL - moduleProvider:(HippyBridgeModuleProviderBlock)block - launchOptions:(NSDictionary *)launchOptions +- (instancetype)initWithDelegate:(nullable id)delegate + bundleURL:(nullable NSURL *)bundleURL + moduleProvider:(nullable HippyBridgeModuleProviderBlock)block + launchOptions:(nullable NSDictionary *)launchOptions executorKey:(nullable NSString *)executorKey { if (self = [super init]) { _delegate = delegate; _moduleProvider = block; _pendingLoadingVendorBundleURL = bundleURL; - _bundleURLs = [NSMutableArray array]; + _allBundleURLs = [NSMutableArray array]; _shareOptions = [NSMutableDictionary dictionary]; - _debugMode = [launchOptions[@"DebugMode"] boolValue]; - if (_debugMode) { - _debugURL = bundleURL; - } - _enableTurbo = !!launchOptions[@"EnableTurbo"] ? [launchOptions[@"EnableTurbo"] boolValue] : YES; + _debugMode = [launchOptions[kHippyLaunchOptionsDebugModeKey] boolValue]; + _debugURL = _debugMode ? bundleURL : nil; + _enableTurbo = !!launchOptions[kHippyLaunchOptionsEnableTurboKey] ? [launchOptions[kHippyLaunchOptionsEnableTurboKey] boolValue] : YES; _engineKey = executorKey.length > 0 ? executorKey : [NSString stringWithFormat:@"%p", self]; HippyLogInfo(@"HippyBridge init begin, self:%p", self); + // Set the log delegate for hippy core module registerLogDelegateToHippyCore(); // Create bundle operation queue - _bundleQueue = [[NSOperationQueue alloc] init]; - _bundleQueue.qualityOfService = NSQualityOfServiceUserInitiated; - _bundleQueue.name = kHippyBundleFetchQueueName; - _bundleQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; + [self prepareBundleQueue]; // Setup [self setUp]; @@ -277,6 +253,37 @@ - (void)dealloc { } } + +#pragma mark - Setup related + +/// Set the log delegate for hippy core module +static inline void registerLogDelegateToHippyCore() { + static dispatch_once_t onceToken; + static const char coreLogkey[] = "tdf"; + dispatch_once(&onceToken, ^{ + footstone::LogMessage::InitializeDelegate([](const std::ostringstream& stream, footstone::LogSeverity severity) { + HippyLogLevel logLevel = HippyLogLevelInfo; + switch (severity) { + case footstone::TDF_LOG_INFO: + logLevel = HippyLogLevelInfo; + break; + case footstone::TDF_LOG_WARNING: + logLevel = HippyLogLevelWarning; + break; + case footstone::TDF_LOG_ERROR: + logLevel = HippyLogLevelError; + break; + case footstone::TDF_LOG_FATAL: + logLevel = HippyLogLevelFatal; + break; + default: + break; + } + HippyLogNativeInternal(logLevel, coreLogkey, 0, @"%s", stream.str().c_str()); + }); + }); +} + - (std::shared_ptr)createURILoaderIfNeeded { if (!_uriLoader) { auto uriHandler = std::make_shared(); @@ -285,152 +292,30 @@ - (void)dealloc { uriLoader->AddConvenientDefaultHandler(uriHandler); auto fileHandler = std::make_shared(self); auto base64DataHandler = std::make_shared(); - uriLoader->RegisterConvenientUriHandler(@"file", fileHandler); - uriLoader->RegisterConvenientUriHandler(@"hpfile", fileHandler); - uriLoader->RegisterConvenientUriHandler(@"data", base64DataHandler); + uriLoader->RegisterConvenientUriHandler(kFileUriScheme, fileHandler); + uriLoader->RegisterConvenientUriHandler(kHpFileUriScheme, fileHandler); + uriLoader->RegisterConvenientUriHandler(kDataUriScheme, base64DataHandler); _uriLoader = uriLoader; } return _uriLoader; } - -#pragma mark - Module Management - -- (NSArray *)moduleClasses { - return _moduleSetup.moduleClasses; -} - -- (id)moduleForName:(NSString *)moduleName { - return [_moduleSetup moduleForName:moduleName]; -} - -- (id)moduleForClass:(Class)moduleClass { - return [_moduleSetup moduleForClass:moduleClass]; -} - -- (HippyModuleData *)moduleDataForName:(NSString *)moduleName { - if (moduleName) { - return _moduleSetup.moduleDataByName[moduleName]; - } - return nil; -} - -- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol { - NSMutableArray *modules = [NSMutableArray new]; - for (Class moduleClass in self.moduleClasses) { - if ([moduleClass conformsToProtocol:protocol]) { - id module = [self moduleForClass:moduleClass]; - if (module) { - [modules addObject:module]; - } - } - } - return [modules copy]; -} - -- (BOOL)moduleIsInitialized:(Class)moduleClass { - return [_moduleSetup isModuleInitialized:moduleClass]; -} - -- (BOOL)moduleSetupComplete { - return _moduleSetup.isModuleSetupComplete; -} - -- (NSDictionary *)nativeModuleConfig { - NSMutableArray *config = [NSMutableArray new]; - for (HippyModuleData *moduleData in [_moduleSetup moduleDataByID]) { - NSArray *moduleDataConfig = [moduleData config]; - [config addObject:HippyNullIfNil(moduleDataConfig)]; - } - return @{ kHippyRemoteModuleConfigKey : config }; -} - -- (NSArray *)configForModuleName:(NSString *)moduleName { - HippyModuleData *moduleData = [_moduleSetup moduleDataByName][moduleName]; - return moduleData.config; -} - -- (HippyOCTurboModule *)turboModuleWithName:(NSString *)name { - if (!self.enableTurbo || name.length <= 0) { - return nil; - } - - if (!self.turboModuleManager) { - self.turboModuleManager = [[HippyTurboModuleManager alloc] initWithBridge:self]; - } - return [self.turboModuleManager turboModuleWithName:name]; -} - - -#pragma mark - Image Config Related - -- (id)imageLoader { - @synchronized (self) { - if (!_imageLoader) { - // Only the last imageloader takes effect, - // compatible with Hippy 2.x - _imageLoader = [[self modulesConformingToProtocol:@protocol(HippyImageCustomLoaderProtocol)] lastObject]; - } - } - return _imageLoader; -} - -- (void)setCustomImageLoader:(id)imageLoader { - @synchronized (self) { - if (imageLoader != _imageLoader) { - if (_imageLoader) { - HippyLogWarn(@"ImageLoader change from %@ to %@", _imageLoader, imageLoader); - } - _imageLoader = imageLoader; - } - } -} - -- (NSArray> *)imageProviders { - @synchronized (self) { - if (!_imageProviders) { - NSMutableArray *moduleClasses = [NSMutableArray new]; - for (Class moduleClass in self.moduleClasses) { - if ([moduleClass conformsToProtocol:@protocol(HippyImageProviderProtocol)]) { - [moduleClasses addObject:moduleClass]; - } +- (void)loadPendingVendorBundleURLIfNeeded { + // Loads the Bundle URL that was passed when the bridge was initialized + if (self.pendingLoadingVendorBundleURL) { + [self loadBundleURL:self.pendingLoadingVendorBundleURL + bundleType:HippyBridgeBundleTypeVendor + completion:^(NSURL * _Nullable bundleURL, NSError * _Nullable error) { + if (error) { + HippyLogError(@"[Hippy_OC_Log][HippyBridge], bundle loaded error:%@, %@", bundleURL, error.description); + } else { + HippyLogInfo(@"[Hippy_OC_Log][HippyBridge], bundle loaded success:%@", bundleURL); } - _imageProviders = moduleClasses; - } - return [_imageProviders copy]; - } -} - -- (void)addImageProviderClass:(Class)cls { - HippyAssertParam(cls); - @synchronized (self) { - _imageProviders = [self.imageProviders arrayByAddingObject:cls]; + }]; } } -#pragma mark - Reload - -- (void)requestReload { - [[NSNotificationCenter defaultCenter] postNotificationName:HippyReloadNotification object:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.invalidateReason = HippyInvalidateReasonReload; - [self invalidate]; - [self setUp]; - }); -} - -#pragma mark - Bridge SetUp - -- (void)setUp { - _valid = YES; - _startTime = footstone::TimePoint::SystemNow(); - - // Get global enviroment info - HippyExecuteOnMainThread(^{ - self->_isOSNightMode = [HippyDeviceBaseInfo isUIScreenInOSDarkMode]; - self.cachedDimensionsInfo = hippyExportedDimensions(self); - }, YES); - +- (void)setupModuleAndJsExecutor { self.moduleSemaphore = dispatch_semaphore_create(0); @try { __weak HippyBridge *weakSelf = self; @@ -455,7 +340,6 @@ - (void)setUp { if (_contextName) { _javaScriptExecutor.contextName = _contextName; } - _displayLink = [[HippyDisplayLink alloc] init]; // Setup all extra and internal modules [_moduleSetup setupModulesWithCompletionBlock:^{ @@ -466,11 +350,48 @@ - (void)setUp { }]; } @catch (NSException *exception) { - HippyBridgeHandleException(exception, self); + HippyHandleException(exception); dispatch_semaphore_signal(self.moduleSemaphore); } +} + +- (void)setVfsUriLoader:(std::weak_ptr)uriLoader { + [_javaScriptExecutor setUriLoader:uriLoader]; +#ifdef ENABLE_INSPECTOR + auto devtools_data_source = _javaScriptExecutor.pScope->GetDevtoolsDataSource(); + auto strongLoader = uriLoader.lock(); + if (devtools_data_source && strongLoader) { + auto notification = devtools_data_source->GetNotificationCenter()->network_notification; + auto devtools_handler = std::make_shared(); + devtools_handler->SetNetworkNotification(notification); + strongLoader->RegisterUriInterceptor(devtools_handler); + } +#endif /* ENABLE_INSPECTOR */ +} + +- (std::weak_ptr)vfsUriLoader { + return _uriLoader; +} + +- (void)setUp { + // Note that this method may be called multiple times, including on bridge reload. + _valid = YES; + _startTime = footstone::TimePoint::SystemNow(); + _displayLink = [[HippyDisplayLink alloc] init]; + // Get global enviroment info + HippyExecuteOnMainThread(^{ + self->_isOSNightMode = [HippyDeviceBaseInfo isUIScreenInOSDarkMode]; + self.cachedDimensionsInfo = hippyExportedDimensions(self); + }, YES); + + // Setup module manager and js executor. + [self setupModuleAndJsExecutor]; + + // Setup default image provider [self addImageProviderClass:[HippyDefaultImageProvider class]]; + + // Setup uri loader [self setVfsUriLoader:[self createURILoaderIfNeeded]]; // Load pending js bundles @@ -479,183 +400,23 @@ - (void)setUp { // Set the default sandbox directory NSString *sandboxDir = [HippyUtils getBaseDirFromResourcePath:_pendingLoadingVendorBundleURL]; [self setSandboxDirectory:sandboxDir]; - } -/// 加载初始化bridge时传入的Bundle URL -- (void)loadPendingVendorBundleURLIfNeeded { - if (self.pendingLoadingVendorBundleURL) { - [self loadBundleURL:self.pendingLoadingVendorBundleURL - bundleType:HippyBridgeBundleTypeVendor - completion:^(NSURL * _Nullable bundleURL, NSError * _Nullable error) { - if (error) { - HippyLogError(@"[Hippy_OC_Log][HippyBridge], bundle loaded error:%@, %@", bundleURL, error.description); - } else { - HippyLogInfo(@"[Hippy_OC_Log][HippyBridge], bundle loaded success:%@", bundleURL); - } - }]; - } -} -#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(whichSelf) \ - @{ kHippyNotiBridgeKey: whichSelf, \ - kHippyNotiBundleUrlKey: bundleURL, \ - kHippyNotiBundleTypeKey : @(bundleType) } - -#define BUNDLE_LOAD_NOTI_ERROR_USER_INFO(whichSelf) \ - @{ kHippyNotiBridgeKey: whichSelf, \ - kHippyNotiBundleUrlKey: bundleURL, \ - kHippyNotiBundleTypeKey : @(bundleType), \ - kHippyNotiErrorKey : error } - -- (void)loadBundleURL:(NSURL *)bundleURL - bundleType:(HippyBridgeBundleType)bundleType - completion:(nonnull HippyBridgeBundleLoadCompletionBlock)completion { - HippyAssertParam(bundleURL); - if (!bundleURL) { - if (completion) { - static NSString *bundleError = @"bundle url is nil"; - NSError *error = [NSError errorWithDomain:@"Bridge Bundle Loading Domain" - code:1 - userInfo:@{NSLocalizedFailureReasonErrorKey: bundleError}]; - completion(nil, error); - } - return; - } - - // bundleURL checking - NSURLComponents *components = [NSURLComponents componentsWithURL:bundleURL resolvingAgainstBaseURL:NO]; - if (components.scheme == nil) { - // If a given url has no scheme, it is considered a file url by default. - components.scheme = @"file"; - bundleURL = components.URL; - } - - HippyLogInfo(@"[HP PERF] Begin loading bundle(%s) at %s", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String), - HP_CSTR_NOT_NULL(bundleURL.absoluteString.UTF8String)); - [_bundleURLs addObject:bundleURL]; - - NSDictionary *userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(self); - [[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScriptWillStartLoadingNotification - object:self - userInfo:userInfo]; - [self beginLoadingBundle:bundleURL bundleType:bundleType completion:completion]; -} +#pragma mark - Lifecycle Related API -- (void)beginLoadingBundle:(NSURL *)bundleURL - bundleType:(HippyBridgeBundleType)bundleType - completion:(HippyBridgeBundleLoadCompletionBlock)completion { - HippyAssertMainQueue(); - HippyAssertParam(bundleURL); - HippyAssertParam(completion); - - __weak __typeof(self)weakSelf = self; - __block NSData *script = nil; - self.loadingCount++; - - // Fetch operation - NSBlockOperation *fetchOperation = [NSBlockOperation blockOperationWithBlock:^{ - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - HippyLogInfo(@"Start fetching bundle(%s)", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); - // create semaphore - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - [strongSelf fetchBundleWithURL:bundleURL completion:^(NSData *source, NSError *error) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if (!strongSelf || !bundleURL) { - return; - } - NSDictionary *userInfo; - if (error) { - HippyBridgeFatal(error, strongSelf); - userInfo = BUNDLE_LOAD_NOTI_ERROR_USER_INFO(strongSelf); - } else { - script = source; - userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(strongSelf); - } - [[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScripDidLoadSourceCodeNotification - object:strongSelf - userInfo:userInfo]; - HippyLogInfo(@"End fetching bundle(%s) error?:%@", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String), error); - dispatch_semaphore_signal(semaphore); // release semaphore - }]; - // wait semaphore - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, HIPPY_BUNDLE_FETCH_TIMEOUT_SEC * NSEC_PER_SEC); - intptr_t result = dispatch_semaphore_wait(semaphore, timeout); - if (result != 0) { - HippyLogError(@"Fetch operation timed out!!! (30s)"); - } - }]; - - // Execution operation - NSBlockOperation *executeOperation = [NSBlockOperation blockOperationWithBlock:^{ - HippyLogInfo(@"Start executing bundle(%s)", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf || !strongSelf.valid || !script) { - NSString *errMsg = [NSString stringWithFormat:@"Bundle Execution Operation Fail! valid:%d, script:%@", - strongSelf.valid, script]; - HippyLogError(@"%@", errMsg); - completion(bundleURL, HippyErrorWithMessage(errMsg)); - @synchronized (self) { - strongSelf.lastExecuteOperation = nil; - } - return; - } - [strongSelf executeJSCode:script sourceURL:bundleURL onCompletion:^(id result, NSError *error) { - HippyLogInfo(@"End executing bundle(%s)", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); - @synchronized (self) { - strongSelf.lastExecuteOperation = nil; - } - if (completion) { - completion(bundleURL, error); - } - if (!strongSelf || !strongSelf.valid) { - return; - } - if (error) { - HippyBridgeFatal(error, strongSelf); - } - - dispatch_async(dispatch_get_main_queue(), ^{ - __strong __typeof(weakSelf)strongSelf = weakSelf; - if (!strongSelf) { - return; - } - strongSelf.loadingCount--; - NSNotificationName notiName = error ? HippyJavaScriptDidFailToLoadNotification : HippyJavaScriptDidLoadNotification; - NSDictionary *userInfo = error ? BUNDLE_LOAD_NOTI_ERROR_USER_INFO(strongSelf) : BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(strongSelf); - [[NSNotificationCenter defaultCenter] postNotificationName:notiName object:strongSelf userInfo:userInfo]; - }); - }]; - }]; - - // Add dependency, make sure that doing fetch before execute, - // and all execution operations must be queued. - [executeOperation addDependency:fetchOperation]; - @synchronized (self) { - NSOperation *lastOp = self.lastExecuteOperation; - if (lastOp) { - [executeOperation addDependency:lastOp]; - } - } - - // Enqueue operation - [_bundleQueue addOperations:@[fetchOperation, executeOperation] waitUntilFinished:NO]; - @synchronized (self) { - self.lastExecuteOperation = executeOperation; - } +- (void)requestReload { + [[NSNotificationCenter defaultCenter] postNotificationName:HippyReloadNotification object:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + self.invalidateReason = HippyInvalidateReasonReload; + [self invalidate]; + [self setUp]; + }); } - (void)unloadInstanceForRootView:(NSNumber *)rootTag { - if (rootTag) { - NSDictionary *param = @{@"id": rootTag}; + if (rootTag != nil) { + NSDictionary *param = @{ kHippyLoadInstanceIdKey : rootTag}; footstone::value::HippyValue value = [param toHippyValue]; std::shared_ptr domValue = std::make_shared(value); if (auto scope = self.javaScriptExecutor.pScope) { @@ -679,111 +440,66 @@ - (void)innerLoadInstanceForRootView:(NSNumber *)rootTag withProperties:(NSDicti HippyAssert(_moduleName, @"module name must not be null"); HippyLogInfo(@"[Hippy_OC_Log][Life_Circle],Running application %@ (%@)", _moduleName, props); HippyLogInfo(@"[HP PERF] Begin loading instance for HippyBridge(%p)", self); - NSDictionary *param = @{@"name": _moduleName, - @"id": rootTag, - @"params": props ?: @{}, - @"version": _HippySDKVersion}; + NSDictionary *param = @{ kHippyLoadInstanceNameKey : _moduleName, + kHippyLoadInstanceIdKey : rootTag, + kHippyLoadInstanceParamsKey : props ?: @{}, + kHippyLoadInstanceVersionKey : _HippySDKVersion }; footstone::value::HippyValue value = [param toHippyValue]; std::shared_ptr domValue = std::make_shared(value); self.javaScriptExecutor.pScope->LoadInstance(domValue); HippyLogInfo(@"[HP PERF] End loading instance for HippyBridge(%p)", self); } -- (void)setVfsUriLoader:(std::weak_ptr)uriLoader { - [_javaScriptExecutor setUriLoader:uriLoader]; -#ifdef ENABLE_INSPECTOR - auto devtools_data_source = _javaScriptExecutor.pScope->GetDevtoolsDataSource(); - auto strongLoader = uriLoader.lock(); - if (devtools_data_source && strongLoader) { - auto notification = devtools_data_source->GetNotificationCenter()->network_notification; - auto devtools_handler = std::make_shared(); - devtools_handler->SetNetworkNotification(notification); - strongLoader->RegisterUriInterceptor(devtools_handler); - } -#endif -} - -- (std::weak_ptr)vfsUriLoader { - return _uriLoader; -} -- (void)setInspectable:(BOOL)isInspectable { - [self.javaScriptExecutor setInspecable:isInspectable]; -} - - -#pragma mark - Private +#pragma mark - Image Config Related -/// Fetch JS Bundle -- (void)fetchBundleWithURL:(NSURL *)bundleURL completion:(void (^)(NSData *source, NSError *error))completion { - HippyAssertParam(bundleURL); - HippyAssertParam(completion); - // Fetch the bundle - // Call the completion handler with the fetched data or error - [self loadContentsAsynchronouslyFromUrl:bundleURL.absoluteString - method:@"get" - params:nil - body:nil - queue:nil - progress:nil - completionHandler:^(NSData * _Nullable data, - NSDictionary * _Nullable userInfo, - NSURLResponse * _Nullable response, - NSError * _Nullable error) { - completion(data, error); - }]; +- (id)imageLoader { + @synchronized (self) { + if (!_imageLoader) { + // Only the last imageloader takes effect, + // compatible with Hippy 2.x + _imageLoader = [[self modulesConformingToProtocol:@protocol(HippyImageCustomLoaderProtocol)] lastObject]; + } + } + return _imageLoader; } -/// Execute JS Bundle -- (void)executeJSCode:(NSData *)script - sourceURL:(NSURL *)sourceURL - onCompletion:(HippyJavaScriptCallback)completion { - if (!script) { - completion(nil, HippyErrorWithMessageAndModuleName(@"no valid data", _moduleName)); - return; - } - if (![self isValid] || !script || !sourceURL) { - completion(nil, HippyErrorWithMessageAndModuleName(@"bridge is not valid", _moduleName)); - return; - } - HippyAssert(self.javaScriptExecutor, @"js executor must not be null"); - __weak __typeof(self)weakSelf = self; - [self.javaScriptExecutor executeApplicationScript:script sourceURL:sourceURL onComplete:^(id result ,NSError *error) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if (!strongSelf || ![strongSelf isValid]) { - completion(result, error); - return; - } - if (error) { - HippyLogError(@"ExecuteApplicationScript Error! %@", error.description); - HippyExecuteOnMainQueue(^{ - __strong __typeof(weakSelf)strongSelf = weakSelf; - [strongSelf stopLoadingWithError:error scriptSourceURL:sourceURL]; - }); +- (void)setCustomImageLoader:(id)imageLoader { + @synchronized (self) { + if (imageLoader != _imageLoader) { + if (_imageLoader) { + HippyLogWarn(@"ImageLoader change from %@ to %@", _imageLoader, imageLoader); + } + _imageLoader = imageLoader; } - completion(result, error); - }]; + } } -- (void)stopLoadingWithError:(NSError *)error scriptSourceURL:(NSURL *)sourceURL { - HippyAssertMainQueue(); - if (![self isValid]) { - return; - } - __weak HippyBridge *weakSelf = self; - [self.javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - @autoreleasepool { - HippyBridge *strongSelf = weakSelf; - if (!strongSelf || ![strongSelf isValid]) { - [strongSelf.javaScriptExecutor invalidate]; +- (NSArray> *)imageProviders { + @synchronized (self) { + if (!_imageProviders) { + NSMutableArray *moduleClasses = [NSMutableArray new]; + for (Class moduleClass in self.moduleClasses) { + if ([moduleClass conformsToProtocol:@protocol(HippyImageProviderProtocol)]) { + [moduleClasses addObject:moduleClass]; + } } + _imageProviders = moduleClasses; } - }]; - if ([error userInfo][HippyJSStackTraceKey]) { - [self.redBox showErrorMessage:[error localizedDescription] withStack:[error userInfo][HippyJSStackTraceKey]]; + return [_imageProviders copy]; + } +} + +- (void)addImageProviderClass:(Class)cls { + HippyAssertParam(cls); + @synchronized (self) { + _imageProviders = [self.imageProviders arrayByAddingObject:cls]; } } + +#pragma mark - Private + - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion { /** @@ -828,8 +544,7 @@ - (void)processResponse:(id)json error:(NSError *)error { withStack:[error userInfo][HippyJSStackTraceKey]]; } } - NSError *retError = HippyErrorFromErrorAndModuleName(error, self.moduleName); - HippyBridgeFatal(retError, self); + HippyBridgeFatal(error, self); } if (![self isValid]) { @@ -1008,7 +723,7 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:( } NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", exception, method.JSMethodName, moduleData.name, params]; - NSError *error = HippyErrorWithMessageAndModuleName(message, self.moduleName); + NSError *error = HippyErrorWithMessage(message); HippyBridgeFatal(error, self); return nil; } @@ -1032,22 +747,13 @@ - (id)callNativeModuleName:(NSString *)moduleName methodName:(NSString *)methodN } NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", exception, method.JSMethodName, module.name, params]; - NSError *error = HippyErrorWithMessageAndModuleName(message, self.moduleName); - HippyBridgeFatal(error, self); + HippyBridgeFatal(HippyErrorWithMessage(message), self); return nil; } } -- (void)setMethodInterceptor:(id)methodInterceptor { - _methodInterceptor = methodInterceptor; -} - -- (id)methodInterceptor { - return _methodInterceptor; -} - - (void)setupDomManager:(std::shared_ptr)domManager - rootNode:(std::weak_ptr)rootNode { + rootNode:(std::weak_ptr)rootNode { HippyAssertParam(domManager); if (!domManager) { return; @@ -1063,22 +769,13 @@ - (void)setupDomManager:(std::shared_ptr)domManager #endif } -- (BOOL)isValid { - return _valid; -} - -- (BOOL)isLoading { - NSUInteger count = self.loadingCount; - return 0 == count; -} - - (void)invalidate { HippyLogInfo(@"[Hippy_OC_Log][Life_Circle],%@ invalide %p", NSStringFromClass([self class]), self); if (![self isValid]) { return; } _valid = NO; - [_bundleURLs removeAllObjects]; + [_allBundleURLs removeAllObjects]; if ([self.delegate respondsToSelector:@selector(invalidateForReason:bridge:)]) { [self.delegate invalidateForReason:self.invalidateReason bridge:self]; } @@ -1191,7 +888,7 @@ - (NSDictionary *)deviceInfo { } -#pragma mark - +#pragma mark - App UI State Related static NSString *const hippyOnNightModeChangedEvent = @"onNightModeChanged"; static NSString *const hippyOnNightModeChangedParam1 = @"NightMode"; @@ -1212,7 +909,11 @@ - (void)setOSNightMode:(BOOL)isOSNightMode withRootViewTag:(nonnull NSNumber *)r } -#pragma mark - +#pragma mark - Debug and Others + +- (void)setInspectable:(BOOL)isInspectable { + [self.javaScriptExecutor setInspecable:isInspectable]; +} - (void)setRedBoxShowEnabled:(BOOL)enabled { #if HIPPY_DEBUG @@ -1243,7 +944,7 @@ - (void)setSandboxDirectory:(NSString *)sandboxDirectory { } - (NSArray *)bundleURLs { - return [_bundleURLs copy]; + return [_allBundleURLs copy]; } - (void)setContextName:(NSString *)contextName { @@ -1282,7 +983,7 @@ - (void)setSnapShotData:(NSData *)data { } -#pragma mark - +#pragma mark - RootView Related - (void)setRootView:(UIView *)rootView { auto engineResource = [[HippyJSEnginesMapper defaultInstance] JSEngineResourceForKey:self.engineKey]; @@ -1358,10 +1059,8 @@ - (void)resetRootSize:(CGSize)size { @end void HippyBridgeFatal(NSError *error, HippyBridge *bridge) { - HippyFatal(error); + // To maintain compatibility with hippy2, + // the underlying API here does not extend the bridge parameter, + // so we pass moduleName to distinguish which bridge we belong to. + HippyFatal(HippyErrorFromErrorAndModuleName(error, bridge.moduleName)); } - -void HippyBridgeHandleException(NSException *exception, HippyBridge *bridge) { - HippyHandleException(exception); -} - diff --git a/framework/ios/base/executors/HippyJSExecutor.mm b/framework/ios/base/executors/HippyJSExecutor.mm index 6245d2422b2..d401c9c6cca 100644 --- a/framework/ios/base/executors/HippyJSExecutor.mm +++ b/framework/ios/base/executors/HippyJSExecutor.mm @@ -39,6 +39,7 @@ #import "NSObject+CtxValue.h" #import "TypeConverter.h" #import "HippyBridge+Private.h" +#import "HippyBridge+ModuleManage.h" #include #include @@ -604,7 +605,7 @@ - (void)_executeJSCall:(NSString *)method [userInfo setObject:moduleName forKey:HippyFatalModuleName]; [userInfo setObject:arguments?:[NSArray array] forKey:@"arguments"]; NSException *reportException = [NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo]; - HippyBridgeHandleException(reportException, self.bridge); + HippyHandleException(reportException); } } }]; diff --git a/framework/ios/base/modules/HippyEventDispatcher.mm b/framework/ios/base/modules/HippyEventDispatcher.mm index 5ce0fffa484..3845153114a 100644 --- a/framework/ios/base/modules/HippyEventDispatcher.mm +++ b/framework/ios/base/modules/HippyEventDispatcher.mm @@ -23,6 +23,7 @@ #import "HippyEventDispatcher.h" #import "HippyAssert.h" #import "HippyUtils.h" +#import "HippyBridge+ModuleManage.h" const NSInteger HippyTextUpdateLagWarningThreshold = 3; diff --git a/framework/ios/base/modules/HippyModuleData.mm b/framework/ios/base/modules/HippyModuleData.mm index 71fd86bf74c..ad9ee2e6908 100644 --- a/framework/ios/base/modules/HippyModuleData.mm +++ b/framework/ios/base/modules/HippyModuleData.mm @@ -22,11 +22,11 @@ #import "HippyModuleData.h" #import "HippyBridge.h" +#import "HippyBridge+ModuleManage.h" #import "HippyModuleMethod.h" #import "HippyAssert.h" #import "HippyLog.h" #import "HippyUtils.h" - #import @@ -121,7 +121,7 @@ - (void)setUpInstanceAndBridge { // This is called outside of the lock in order to prevent deadlock issues // because the logic in `finishSetupForInstance` can cause // `moduleData.instance` to be accessed re-entrantly. - if (_bridge.moduleSetupComplete) { + if (_bridge.isModuleSetupComplete) { [self finishSetupForInstance]; } else { // If we're here, then the module is completely initialized, diff --git a/framework/ios/module/dev/HippyDevMenu.mm b/framework/ios/module/dev/HippyDevMenu.mm index 09369a570d6..1a84cc2719d 100644 --- a/framework/ios/module/dev/HippyDevMenu.mm +++ b/framework/ios/module/dev/HippyDevMenu.mm @@ -27,7 +27,7 @@ #import "HippyAssert.h" #import "HippyUtils.h" #import "HippyDefines.h" - +#import "HippyBridge+ModuleManage.h" #include #if HIPPY_DEV diff --git a/framework/ios/module/dev/HippyRedBox.mm b/framework/ios/module/dev/HippyRedBox.mm index 05b4d84c603..ad10a9aaf55 100644 --- a/framework/ios/module/dev/HippyRedBox.mm +++ b/framework/ios/module/dev/HippyRedBox.mm @@ -21,6 +21,7 @@ */ #import "HippyBridge.h" +#import "HippyBridge+ModuleManage.h" #import "HippyErrorInfo.h" #import "HippyRedBox.h" #import "HippyUtils.h" diff --git a/framework/ios/module/turbo/HippyOCTurboModule.mm b/framework/ios/module/turbo/HippyOCTurboModule.mm index 8a8d3274449..c520a2bb9e5 100644 --- a/framework/ios/module/turbo/HippyOCTurboModule.mm +++ b/framework/ios/module/turbo/HippyOCTurboModule.mm @@ -30,7 +30,7 @@ #import "HippyUtils.h" #import "NSObject+CtxValue.h" #import "NSObject+HippyTurbo.h" - +#import "HippyBridge+ModuleManage.h" #include #include "footstone/string_view_utils.h" @@ -135,7 +135,7 @@ - (id)invokeObjCMethodWithName:(NSString *)methodName NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown while invoking %@ on target %@ with params %@", exception, method.JSMethodName, NSStringFromClass([self class]) ,argumentArray]; NSError *error = HippyErrorWithMessageAndModuleName(message, self.bridge.moduleName); - HippyBridgeFatal(error, self.bridge); + HippyFatal(error); return nil; } } diff --git a/modules/ios/base/HippyDefines.h b/modules/ios/base/HippyDefines.h index 46b32e34a82..21dd784b8e1 100644 --- a/modules/ios/base/HippyDefines.h +++ b/modules/ios/base/HippyDefines.h @@ -67,6 +67,11 @@ #define HIPPY_CONCAT2(A, B) A##B #define HIPPY_CONCAT(A, B) HIPPY_CONCAT2(A, B) +/** + * Make sure C string non null. + */ +#define HIPPY_CSTR_NOT_NULL(p) (p ? p : "") + /** * Convert number macro to string */ diff --git a/renderer/native/ios/renderer/HippyComponent.h b/renderer/native/ios/renderer/HippyComponent.h index 9c44b1af7b4..b630400b362 100644 --- a/renderer/native/ios/renderer/HippyComponent.h +++ b/renderer/native/ios/renderer/HippyComponent.h @@ -101,5 +101,5 @@ typedef void (^HippyDirectEventBlock)(NSDictionary *body); /// Hippy use multiple of 10 as tag of root view /// - Parameter hippyTag: hippy tag static inline BOOL HippyIsHippyRootView(NSNumber *hippyTag) { - return hippyTag ? hippyTag.integerValue % 10 == 0 : false; + return (hippyTag != nil) ? hippyTag.integerValue % 10 == 0 : false; } diff --git a/renderer/native/ios/renderer/HippyRootView.mm b/renderer/native/ios/renderer/HippyRootView.mm index 29b4987d2ae..95e90d40da1 100644 --- a/renderer/native/ios/renderer/HippyRootView.mm +++ b/renderer/native/ios/renderer/HippyRootView.mm @@ -27,6 +27,7 @@ #import "HippyInvalidating.h" #import "HippyBridge.h" #import "Hippybridge+PerformanceAPI.h" +#import "HippyBridge+BundleLoad.h" #import "HippyUIManager.h" #import "HippyUtils.h" #import "HippyDeviceBaseInfo.h" diff --git a/renderer/native/ios/renderer/HippyUIManager.h b/renderer/native/ios/renderer/HippyUIManager.h index 519abc4e5a4..734bb97573f 100644 --- a/renderer/native/ios/renderer/HippyUIManager.h +++ b/renderer/native/ios/renderer/HippyUIManager.h @@ -146,12 +146,12 @@ HIPPY_EXTERN NSString *const HippyFontChangeTriggerNotification; /** - * Manully create views recursively from renderObject + * Manully create views recursively from shadowView * - * @param renderObject HippyShadowView corresponding to UIView + * @param shadowView HippyShadowView corresponding to UIView * @return view created by HippyShadowView */ -- (UIView *)createViewForShadowListItem:(HippyShadowView *)renderObject; +- (UIView *)createViewForShadowListItem:(HippyShadowView *)shadowView; /// Register extra components /// @param extraComponents extra components classes diff --git a/renderer/native/ios/renderer/HippyUIManager.mm b/renderer/native/ios/renderer/HippyUIManager.mm index 99ab327efb3..843e03f263a 100644 --- a/renderer/native/ios/renderer/HippyUIManager.mm +++ b/renderer/native/ios/renderer/HippyUIManager.mm @@ -43,6 +43,7 @@ #import "UIView+Hippy.h" #import "HippyBridgeModule.h" #import "HippyModulesSetup.h" +#import "HippyBridge+ModuleManage.h" #import "NativeRenderManager.h" #import "HippyShadowListView.h" #import "HippyModuleData.h" @@ -52,7 +53,7 @@ #import "HippyShadowText.h" #import "HippyShadowTextView.h" #import "dom/root_node.h" -#import "objc/runtime.h" +#import #import #import @@ -454,7 +455,6 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view{ } if (!HippyCGRectRoundInPixelNearlyEqual(frame, renderObject.frame)) { - //renderObject.frame = frame; [renderObject setLayoutFrame:frame]; std::weak_ptr rootNode = [strongSelf->_shadowViewRegistry rootNodeForTag:rootTag]; [strongSelf batchOnRootNode:rootNode]; diff --git a/tests/ios/HippyBridgeTest.mm b/tests/ios/HippyBridgeTest.mm index 72c7adfb4a6..7aed9870a47 100644 --- a/tests/ios/HippyBridgeTest.mm +++ b/tests/ios/HippyBridgeTest.mm @@ -22,23 +22,42 @@ #import #import +#import +#import +#import "HippyBridge+Private.h" +#import + + +@interface HippyBridge (UnitTestForBundleLoad) + +/// Execute JS Bundle +- (void)executeJSCode:(NSData *)script + sourceURL:(NSURL *)sourceURL + onCompletion:(HippyJavaScriptCallback)completion; + +@end @interface HippyBridgeTest : XCTestCase +/// bridge instance +@property (nonatomic, strong) HippyBridge *bridge; + @end @implementation HippyBridgeTest - (void)setUp { // Put setup code here. This method is called before the invocation of each test method in the class. + self.bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. + self.bridge = nil; } - (void)testLoadBundleURL { - HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; + HippyBridge *bridge = self.bridge; NSString *testNoSchemePath = @"/Users/ray/testNoSchemePath"; NSURL *testUrl = [NSURL URLWithString:testNoSchemePath]; XCTAssert(testUrl.scheme == nil); @@ -59,5 +78,69 @@ - (void)testLoadBundleURL { XCTAssertFalse(loadedUrl.isFileURL); } +- (void)testPrepareBundleQueue { + HippyBridge *bridge = self.bridge; + XCTAssertNotNil(bridge.bundleQueue); + XCTAssert(bridge.bundleQueue.qualityOfService == NSQualityOfServiceUserInitiated); +} + +- (void)testIsLoading { + HippyBridge *bridge = self.bridge; + id mockBridge = OCMPartialMock(bridge); + // loading count greater than 0 + OCMStub([mockBridge loadingCount]).andReturn(1); + XCTAssertTrue([mockBridge isLoading]); + // loading count is 0 + HippyBridge *bridge2 = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; + id mockBridge2 = OCMPartialMock(bridge2); + OCMStub([mockBridge2 loadingCount]).andReturn(0); + XCTAssertFalse([mockBridge2 isLoading]); +} + +- (void)testExecuteJSCodeCallsCompletionWithErrorWhenScriptIsNil { + // Create an expectation to wait for the async callback + XCTestExpectation *expectation = [self expectationWithDescription:@"Completion handler called"]; + + // Define the completion callback + HippyJavaScriptCallback completion = ^(id result, NSError *error) { + XCTAssertNotNil(error, @"Error should not be nil when script is nil"); + XCTAssertNotNil(error.localizedDescription); + [expectation fulfill]; + }; + + + // Call executeJSCode with nil script + HippyBridge *bridge = self.bridge; + [bridge executeJSCode:nil sourceURL:nil onCompletion:completion]; + + // Wait for the async callback + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} + + +- (void)testExecuteJSCodeCallsJavaScriptExecutor { + // Create an instance of HippyBridge and mock the JavaScript executor + HippyBridge *bridge = self.bridge; + id mockBridge = OCMPartialMock(bridge); + id mockJavaScriptExecutor = OCMClassMock([HippyJSExecutor class]); + OCMStub([mockBridge javaScriptExecutor]).andReturn(mockJavaScriptExecutor); + + // Stub isValid to return YES + OCMStub([mockBridge isValid]).andReturn(YES); + + // Expect the executeApplicationScript method to be called + OCMExpect([mockJavaScriptExecutor executeApplicationScript:[OCMArg any] + sourceURL:[OCMArg any] + onComplete:[OCMArg any]]); + + // Call executeJSCode with valid script and sourceURL + NSData *scriptData = [@"console.log('Hello, World!');" dataUsingEncoding:NSUTF8StringEncoding]; + NSURL *sourceURL = [NSURL URLWithString:@"http://example.com/script.js"]; + [mockBridge executeJSCode:scriptData sourceURL:sourceURL onCompletion:nil]; + + // Verify that executeApplicationScript was called + OCMVerifyAll(mockJavaScriptExecutor); +} + @end diff --git a/tests/ios/HippyUIManagerTest.mm b/tests/ios/HippyUIManagerTest.mm index 5255a3f278e..a93b34d9b49 100644 --- a/tests/ios/HippyUIManagerTest.mm +++ b/tests/ios/HippyUIManagerTest.mm @@ -49,7 +49,7 @@ - (void)tearDown { } - (void)testGetComponentDataForViewName { - HippyBridge *bridge = [[HippyBridge alloc] init]; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; HippyUIManager *manager = [[HippyUIManager alloc] initWithBridge:bridge]; [manager registerExtraComponent:@[ HippyViewManager.class ]]; diff --git a/tests/ios/HippyUIViewCategoryTest.m b/tests/ios/HippyUIViewCategoryTest.m index d1a879d1a31..b2553516839 100644 --- a/tests/ios/HippyUIViewCategoryTest.m +++ b/tests/ios/HippyUIViewCategoryTest.m @@ -63,7 +63,7 @@ - (void)testGetHippyRootView { - (void)testGetHippyUIManager { UIView *testView = [UIView new]; XCTAssertNil([testView uiManager]); - HippyBridge *bridge = [[HippyBridge alloc] init]; + HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil]; HippyUIManager *uiManager = [[HippyUIManager alloc] initWithBridge:bridge]; XCTAssertNoThrow(testView.uiManager = uiManager); XCTAssertTrue(testView.uiManager == uiManager);