Skip to content

Commit

Permalink
refactor(ios): rewrite bundle loading process (#4028)
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwcg authored Sep 12, 2024
1 parent 2bc99d9 commit c2927bb
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 765 deletions.
226 changes: 118 additions & 108 deletions framework/ios/base/bridge/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

#import "HippyBridge.h"
#import "HippyBridge+Private.h"
#import "HippyBundleLoadOperation.h"
#import "HippyBundleExecutionOperation.h"
#import "HippyBundleOperationQueue.h"
#import "HippyDeviceBaseInfo.h"
#import "HippyDisplayLink.h"
#import "HippyEventDispatcher.h"
Expand All @@ -49,6 +46,7 @@
#import "HippyUtils.h"
#import "TypeConverter.h"
#import "VFSUriLoader.h"
#import "HippyBridge+VFSLoader.h"
#import "HippyBase64DataHandler.h"
#import "NativeRenderManager.h"
#import "HippyRootView.h"
Expand Down Expand Up @@ -109,6 +107,10 @@
static NSString *const kHippyBatchedBridgeConfigKey = @"__hpBatchedBridgeConfig";


#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";

typedef NS_ENUM(NSUInteger, HippyBridgeFields) {
HippyBridgeFieldRequestModuleIDs = 0,
HippyBridgeFieldMethodIDs,
Expand Down Expand Up @@ -148,12 +150,10 @@ static inline void registerLogDelegateToHippyCore() {
@interface HippyBridge() {
__weak id<HippyMethodInterceptorProtocol> _methodInterceptor;
HippyModulesSetup *_moduleSetup;
__weak NSOperation *_lastOperation;
BOOL _wasBatchActive;
HippyDisplayLink *_displayLink;
HippyBridgeModuleProviderBlock _moduleProvider;
BOOL _valid;
HippyBundleOperationQueue *_bundlesQueue;
NSMutableArray<NSURL *> *_bundleURLs;

std::shared_ptr<VFSUriLoader> _uriLoader;
Expand All @@ -173,14 +173,19 @@ @interface HippyBridge() {
/// 在共享情况下,只有全部bridge实例均释放,JS引擎资源才会销毁。
/// 默认情况下对每个bridge使用独立JS引擎
@property (nonatomic, strong) NSString *engineKey;
/// 等待加载(Load)的 Vendor bundleURL
@property (nonatomic, strong) NSURL *pendingLoadingVendorBundleURL;

@property(readwrite, strong) dispatch_semaphore_t moduleSemaphore;
@property(readwrite, assign) NSInteger loadingCount;

/// Module setup semaphore
@property (readwrite, strong) dispatch_semaphore_t moduleSemaphore;

/// 缓存的Dimensions信息,用于传递给JS Side
/// 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 (atomic, strong, nullable) NSOperation *lastExecuteOperation;

/// Cached Dimensions info,will be passed to JS Side.
@property (atomic, strong) NSDictionary *cachedDimensionsInfo;

@end
Expand All @@ -193,17 +198,6 @@ @implementation HippyBridge

dispatch_queue_t HippyJSThread;

dispatch_queue_t HippyBridgeQueue() {
static dispatch_once_t onceToken;
static dispatch_queue_t queue;
dispatch_once(&onceToken, ^{
dispatch_queue_attr_t attr =
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queue = dispatch_queue_create("com.hippy.bridge", attr);
});
return queue;
}

+ (void)initialize {
[super initialize];
static dispatch_once_t onceToken;
Expand Down Expand Up @@ -238,13 +232,16 @@ - (instancetype)initWithDelegate:(id<HippyBridgeDelegate>)delegate
_debugMode = [launchOptions[@"DebugMode"] boolValue];
_enableTurbo = !!launchOptions[@"EnableTurbo"] ? [launchOptions[@"EnableTurbo"] boolValue] : YES;
_engineKey = executorKey.length > 0 ? executorKey : [NSString stringWithFormat:@"%p", self];
_bundlesQueue = [[HippyBundleOperationQueue alloc] init];

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;

// Setup
[self setUp];

Expand Down Expand Up @@ -462,6 +459,7 @@ - (void)setUp {

} @catch (NSException *exception) {
HippyBridgeHandleException(exception, self);
dispatch_semaphore_signal(self.moduleSemaphore);
}

[self addImageProviderClass:[HippyDefaultImageProvider class]];
Expand Down Expand Up @@ -491,20 +489,21 @@ - (void)loadPendingVendorBundleURLIfNeeded {
}
}

#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO \
@{ kHippyNotiBridgeKey: strongSelf, \
#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(whichSelf) \
@{ kHippyNotiBridgeKey: whichSelf, \
kHippyNotiBundleUrlKey: bundleURL, \
kHippyNotiBundleTypeKey : @(bundleType) }

#define BUNDLE_LOAD_NOTI_ERROR_USER_INFO \
@{ kHippyNotiBridgeKey: strongSelf, \
#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";
Expand All @@ -529,117 +528,108 @@ - (void)loadBundleURL:(NSURL *)bundleURL
HP_CSTR_NOT_NULL(bundleURL.absoluteString.UTF8String));
[_bundleURLs addObject:bundleURL];

__weak __typeof(self)weakSelf = self;
dispatch_async(HippyBridgeQueue(), ^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSDictionary *userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO;
[[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScriptWillStartLoadingNotification
object:strongSelf
userInfo:userInfo];
[strongSelf beginLoadingBundle:bundleURL bundleType:bundleType completion:completion];
});
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 {
dispatch_group_t group = dispatch_group_create();
__weak HippyBridge *weakSelf = self;
HippyAssertMainQueue();
HippyAssertParam(bundleURL);
HippyAssertParam(completion);

__weak __typeof(self)weakSelf = self;
__block NSData *script = nil;
self.loadingCount++;
dispatch_group_enter(group);
NSOperationQueue *bundleQueue = [[NSOperationQueue alloc] init];
bundleQueue.maxConcurrentOperationCount = 1;
bundleQueue.name = @"com.hippy.bundleQueue";
HippyBundleLoadOperation *fetchOp = [[HippyBundleLoadOperation alloc] initWithBridge:self
bundleURL:bundleURL
queue:bundleQueue];
fetchOp.onLoad = ^(NSData *source, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;

// Fetch operation
NSBlockOperation *fetchOperation = [NSBlockOperation blockOperationWithBlock:^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
dispatch_group_leave(group);
return;
}
NSDictionary *userInfo;
if (error) {
HippyBridgeFatal(error, weakSelf);
userInfo = BUNDLE_LOAD_NOTI_ERROR_USER_INFO;
} else {
script = source;
userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO;
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;
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)");
}
[[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScripDidLoadSourceCodeNotification
object:strongSelf
userInfo:userInfo];
dispatch_group_leave(group);
};
}];

dispatch_group_enter(group);
HippyBundleExecutionOperation *executeOp = [[HippyBundleExecutionOperation alloc] initWithBlock:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf || !strongSelf.valid) {
dispatch_group_leave(group);
// 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];
completion(nil, HippyErrorWithMessage(errMsg));
strongSelf.lastExecuteOperation = nil;
return;
}
__weak __typeof(strongSelf)weakSelf = strongSelf;
[strongSelf executeJSCode:script sourceURL:bundleURL onCompletion:^(id result, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
HippyLogInfo(@"End loading bundle(%s) at %s",
HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String),
HP_CSTR_NOT_NULL(bundleURL.absoluteString.UTF8String));

HippyLogInfo(@"End executing bundle(%s)",
HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String));
strongSelf.lastExecuteOperation = nil;
if (completion) {
completion(bundleURL, error);
}
if (!strongSelf || !strongSelf.valid) {
dispatch_group_leave(group);
return;
}
if (error) {
HippyBridgeFatal(error, strongSelf);
}
__weak __typeof(self)weakSelf = strongSelf;

dispatch_async(dispatch_get_main_queue(), ^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSNotificationName notiName;
NSDictionary *userInfo;
if (error) {
notiName = HippyJavaScriptDidFailToLoadNotification;
userInfo = BUNDLE_LOAD_NOTI_ERROR_USER_INFO;
} else {
notiName = HippyJavaScriptDidLoadNotification;
userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO;
}
[[NSNotificationCenter defaultCenter] postNotificationName:notiName
object:strongSelf
userInfo:userInfo];
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];
});
dispatch_group_leave(group);
}];
} queue:bundleQueue];
}];

//set dependency
[executeOp addDependency:fetchOp];
if (_lastOperation) {
[executeOp addDependency:_lastOperation];
_lastOperation = executeOp;
} else {
_lastOperation = executeOp;
// Add dependency, make sure that doing fetch before execute,
// and all execution operations must be queued.
[executeOperation addDependency:fetchOperation];
if (self.lastExecuteOperation) {
[executeOperation addDependency:self.lastExecuteOperation];
}
[_bundlesQueue addOperations:@[fetchOp, executeOp]];
dispatch_block_t completionBlock = ^(void){
HippyBridge *strongSelf = weakSelf;
if (strongSelf && strongSelf.isValid) {
strongSelf.loadingCount--;
}
};
dispatch_group_notify(group, HippyBridgeQueue(), completionBlock);

// Enqueue operation
[_bundleQueue addOperations:@[fetchOperation, executeOperation] waitUntilFinished:NO];
self.lastExecuteOperation = executeOperation;
}

- (void)unloadInstanceForRootView:(NSNumber *)rootTag {
Expand Down Expand Up @@ -703,6 +693,26 @@ - (void)setInspectable:(BOOL)isInspectable {

#pragma mark - Private

/// 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
Expand Down

This file was deleted.

Loading

0 comments on commit c2927bb

Please sign in to comment.