From 892b5fd3e96629c529472a33841f1d789d191aec Mon Sep 17 00:00:00 2001 From: wwwcg Date: Thu, 24 Oct 2024 17:41:51 +0800 Subject: [PATCH] Revert "refactor(ios): rewrite bundle loading process (#4028)" This reverts commit b91f3e9e9ee03e61b97cfe509bd237b772e6158f. --- framework/ios/base/bridge/HippyBridge.mm | 224 +++++++-------- .../HippyBundleExecutionOperation.h | 33 +++ .../HippyBundleExecutionOperation.m | 258 ++++++++++++++++++ .../HippyBundleLoadOperation.h | 39 +++ .../HippyBundleLoadOperation.mm | 207 ++++++++++++++ .../HippyBundleOperationQueue.h | 33 +++ .../HippyBundleOperationQueue.m | 87 ++++++ 7 files changed, 763 insertions(+), 118 deletions(-) create mode 100644 framework/ios/base/bundleoperations/HippyBundleExecutionOperation.h create mode 100644 framework/ios/base/bundleoperations/HippyBundleExecutionOperation.m create mode 100644 framework/ios/base/bundleoperations/HippyBundleLoadOperation.h create mode 100644 framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm create mode 100644 framework/ios/base/bundleoperations/HippyBundleOperationQueue.h create mode 100644 framework/ios/base/bundleoperations/HippyBundleOperationQueue.m diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index 59d2fb6d832..26e02abe01f 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -22,6 +22,9 @@ #import "HippyBridge.h" #import "HippyBridge+Private.h" +#import "HippyBundleLoadOperation.h" +#import "HippyBundleExecutionOperation.h" +#import "HippyBundleOperationQueue.h" #import "HippyContextWrapper.h" #import "HippyDeviceBaseInfo.h" #import "HippyDisplayLink.h" @@ -48,7 +51,6 @@ #import "UIView+RenderManager.h" #import "TypeConverter.h" #import "VFSUriLoader.h" -#import "HippyBridge+VFSLoader.h" #import "HippyBase64DataHandler.h" #import "NativeRenderManager.h" #import "HippyRootView.h" @@ -102,10 +104,6 @@ static NSString *const HippyNativeGlobalKeyNightMode = @"NightMode"; -#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, @@ -145,10 +143,12 @@ static inline void registerLogDelegateToHippyCore() { @interface HippyBridge() { __weak id _methodInterceptor; HippyModulesSetup *_moduleSetup; + __weak NSOperation *_lastOperation; BOOL _wasBatchActive; HippyDisplayLink *_displayLink; HippyBridgeModuleProviderBlock _moduleProvider; BOOL _valid; + HippyBundleOperationQueue *_bundlesQueue; NSMutableArray *_bundleURLs; NSURL *_sandboxDirectory; @@ -165,19 +165,14 @@ @interface HippyBridge() { /// 在共享情况下,只有全部bridge实例均释放,JS引擎资源才会销毁。 /// 默认情况下对每个bridge使用独立JS引擎 @property (nonatomic, strong) NSString *engineKey; -/// Module setup semaphore -@property (readwrite, strong) dispatch_semaphore_t moduleSemaphore; - -/// Pending load bundle's URL +/// 等待加载(Load)的 Vendor bundleURL @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(readwrite, strong) dispatch_semaphore_t moduleSemaphore; +@property(readwrite, assign) NSInteger loadingCount; + + +/// 缓存的Dimensions信息,用于传递给JS Side @property (atomic, strong) NSDictionary *cachedDimensionsInfo; @end @@ -191,6 +186,17 @@ @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; @@ -227,6 +233,7 @@ - (instancetype)initWithDelegate:(id)delegate _engineKey = executorKey.length > 0 ? executorKey : [NSString stringWithFormat:@"%p", self]; _invalidateReason = HippyInvalidateReasonDealloc; _valid = YES; + _bundlesQueue = [[HippyBundleOperationQueue alloc] init]; _startTime = footstone::TimePoint::SystemNow(); HippyLogInfo(@"HippyBridge init begin, self:%p", self); registerLogDelegateToHippyCore(); @@ -236,12 +243,6 @@ - (instancetype)initWithDelegate:(id)delegate self.cachedDimensionsInfo = hippyExportedDimensions(self); }, YES); - // Create bundle operation queue - _bundleQueue = [[NSOperationQueue alloc] init]; - _bundleQueue.qualityOfService = NSQualityOfServiceUserInitiated; - _bundleQueue.name = kHippyBundleFetchQueueName; - _bundleQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; - [self setUp]; [self addImageProviderClass:[HippyDefaultImageProvider class]]; @@ -439,7 +440,6 @@ - (void)setUp { } @catch (NSException *exception) { HippyBridgeHandleException(exception, self); - dispatch_semaphore_signal(self.moduleSemaphore); } } @@ -459,13 +459,13 @@ - (void)loadPendingVendorBundleURLIfNeeded { } } -#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO(whichSelf) \ - @{ kHippyNotiBridgeKey: whichSelf, \ +#define BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO \ + @{ kHippyNotiBridgeKey: strongSelf, \ kHippyNotiBundleUrlKey: bundleURL, \ kHippyNotiBundleTypeKey : @(bundleType) } -#define BUNDLE_LOAD_NOTI_ERROR_USER_INFO(whichSelf) \ - @{ kHippyNotiBridgeKey: whichSelf, \ +#define BUNDLE_LOAD_NOTI_ERROR_USER_INFO \ + @{ kHippyNotiBridgeKey: strongSelf, \ kHippyNotiBundleUrlKey: bundleURL, \ kHippyNotiBundleTypeKey : @(bundleType), \ kHippyNotiErrorKey : error } @@ -473,7 +473,6 @@ - (void)loadPendingVendorBundleURLIfNeeded { - (void)loadBundleURL:(NSURL *)bundleURL bundleType:(HippyBridgeBundleType)bundleType completion:(nonnull HippyBridgeBundleLoadCompletionBlock)completion { - HippyAssertParam(bundleURL); if (!bundleURL) { if (completion) { static NSString *bundleError = @"bundle url is nil"; @@ -498,108 +497,117 @@ - (void)loadBundleURL:(NSURL *)bundleURL 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]; + __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]; + }); } - (void)beginLoadingBundle:(NSURL *)bundleURL bundleType:(HippyBridgeBundleType)bundleType completion:(HippyBridgeBundleLoadCompletionBlock)completion { - HippyAssertMainQueue(); - HippyAssertParam(bundleURL); - HippyAssertParam(completion); - - __weak __typeof(self)weakSelf = self; + dispatch_group_t group = dispatch_group_create(); + __weak HippyBridge *weakSelf = self; __block NSData *script = nil; self.loadingCount++; - - // Fetch operation - NSBlockOperation *fetchOperation = [NSBlockOperation blockOperationWithBlock:^{ - __strong __typeof(weakSelf) strongSelf = weakSelf; + 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; if (!strongSelf) { + dispatch_group_leave(group); 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; - 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)"); + NSDictionary *userInfo; + if (error) { + HippyBridgeFatal(error, weakSelf); + userInfo = BUNDLE_LOAD_NOTI_ERROR_USER_INFO; + } else { + script = source; + userInfo = BUNDLE_LOAD_NOTI_SUCCESS_USER_INFO; } - }]; + [[NSNotificationCenter defaultCenter] postNotificationName:HippyJavaScripDidLoadSourceCodeNotification + object:strongSelf + userInfo:userInfo]; + 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; + dispatch_group_enter(group); + HippyBundleExecutionOperation *executeOp = [[HippyBundleExecutionOperation alloc] initWithBlock:^{ + __strong __typeof(weakSelf)strongSelf = weakSelf; + if (!strongSelf || !strongSelf.valid) { + dispatch_group_leave(group); return; } + __weak __typeof(strongSelf)weakSelf = strongSelf; [strongSelf executeJSCode:script sourceURL:bundleURL onCompletion:^(id result, NSError *error) { - HippyLogInfo(@"End executing bundle(%s)", - HP_CSTR_NOT_NULL(bundleURL.absoluteString.lastPathComponent.UTF8String)); - strongSelf.lastExecuteOperation = nil; + __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)); + 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; } - 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]; + 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]; }); + dispatch_group_leave(group); }]; - }]; + } queue:bundleQueue]; - // 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]; + //set dependency + [executeOp addDependency:fetchOp]; + if (_lastOperation) { + [executeOp addDependency:_lastOperation]; + _lastOperation = executeOp; + } else { + _lastOperation = executeOp; } - - // Enqueue operation - [_bundleQueue addOperations:@[fetchOperation, executeOperation] waitUntilFinished:NO]; - self.lastExecuteOperation = executeOperation; + [_bundlesQueue addOperations:@[fetchOp, executeOp]]; + dispatch_block_t completionBlock = ^(void){ + HippyBridge *strongSelf = weakSelf; + if (strongSelf && strongSelf.isValid) { + strongSelf.loadingCount--; + } + }; + dispatch_group_notify(group, HippyBridgeQueue(), completionBlock); } - (void)unloadInstanceForRootView:(NSNumber *)rootTag { @@ -667,26 +675,6 @@ - (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 diff --git a/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.h b/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.h new file mode 100644 index 00000000000..c3ccecd4de5 --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.h @@ -0,0 +1,33 @@ +/*! + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface HippyBundleExecutionOperation : NSOperation + +- (instancetype)initWithBlock:(dispatch_block_t)block queue:(NSOperationQueue *)queue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.m b/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.m new file mode 100644 index 00000000000..db2b62ab1e1 --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleExecutionOperation.m @@ -0,0 +1,258 @@ +/*! + * 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 "HippyBundleExecutionOperation.h" + +@interface HippyBundleExecutionOperation () { + dispatch_block_t _block; + BOOL _cancelled; + BOOL _executing; + BOOL _finished; + BOOL _asynchronous; + BOOL _ready; + NSMutableSet *_dependencies; + + dispatch_semaphore_t _statusSem; + dispatch_semaphore_t _dependencySem; +} + +@end + +@implementation HippyBundleExecutionOperation + +- (instancetype)initWithBlock:(dispatch_block_t)block queue:(NSOperationQueue *)queue { + self = [super init]; + if (self) { + _block = [block copy]; + _dependencies = [NSMutableSet setWithCapacity:8]; + _statusSem = dispatch_semaphore_create(1); + _dependencySem = dispatch_semaphore_create(1); + self.ready = YES; + } + return self; +} + +- (void)dealloc { + dispatch_semaphore_wait(_dependencySem, DISPATCH_TIME_FOREVER); + for (NSOperation *op in _dependencies) { + [op removeObserver:self forKeyPath:@"finished" context:NULL]; + } + dispatch_semaphore_signal(_dependencySem); +} + +- (void)start { + if (self.cancelled) { + return; + } + if (self.ready) { + [self main]; + } +} + +- (void)main { + if (self.cancelled) { + return; + } + self.finished = NO; + self.executing = YES; + if (_block) { + _block(); + } + self.finished = YES; + self.executing = NO; +} + +- (void)addDependency:(NSOperation *)op { + if (self.cancelled || self.executing || self.finished || !op) { + return; + } + { + dispatch_semaphore_wait(_dependencySem, DISPATCH_TIME_FOREVER); + [_dependencies addObject:op]; + dispatch_semaphore_signal(_dependencySem); + } + if (![op isFinished]) { + self.ready = NO; + } + [op addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:NULL]; +} + +- (void)removeDependency:(NSOperation *)op { + dispatch_semaphore_wait(_dependencySem, DISPATCH_TIME_FOREVER); + if ([_dependencies containsObject:op]) { + [_dependencies removeObject:op]; + [op removeObserver:self forKeyPath:@"finished" context:NULL]; + } + dispatch_semaphore_signal(_dependencySem); +} + +- (NSArray *)dependencies { + dispatch_semaphore_wait(_dependencySem, DISPATCH_TIME_FOREVER); + NSArray *objects = [_dependencies allObjects]; + dispatch_semaphore_signal(_dependencySem); + return objects; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqualToString:@"finished"]) { + [self checkForReadyStatus]; + } +} + +- (void)checkForReadyStatus { + dispatch_semaphore_wait(_dependencySem, DISPATCH_TIME_FOREVER); + BOOL status = YES; + for (NSOperation *op in _dependencies) { + if (![op isFinished]) { + status = NO; + break; + } + } + self.ready = status; + dispatch_semaphore_signal(_dependencySem); +} + +- (void)cancel { + self.cancelled = YES; +} + +- (void)setCancelled:(BOOL)cancelled { + if (_cancelled == cancelled) { + return; + } + [self willChangeValueForKey:@"cancelled"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _cancelled = cancelled; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"cancelled"]; +} + +- (BOOL)isCancelled { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL cancel = _cancelled; + dispatch_semaphore_signal(_statusSem); + return cancel; +} + +- (void)setExecuting:(BOOL)isExecuting { + if (_executing == isExecuting) { + return; + } + [self willChangeValueForKey:@"executing"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _executing = isExecuting; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"executing"]; +} + +- (BOOL)isExecuting { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL executing = _executing; + dispatch_semaphore_signal(_statusSem); + return executing; +} + +- (void)setFinished:(BOOL)isFinished { + if (_finished == isFinished) { + return; + } + [self willChangeValueForKey:@"finished"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _finished = isFinished; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"finished"]; +} + +- (BOOL)isFinished { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL finished = _finished; + dispatch_semaphore_signal(_statusSem); + return finished; +} + +- (void)setConcurrent:(BOOL)isConcurrent { + if (_asynchronous == isConcurrent) { + return; + } + [self willChangeValueForKey:@"concurrent"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _asynchronous = isConcurrent; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"concurrent"]; +} + +- (BOOL)isConcurrent { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL asynchronous = _asynchronous; + dispatch_semaphore_signal(_statusSem); + return asynchronous; +} + +- (void)setAsynchronous:(BOOL)isAsynchronous { + if (_asynchronous == isAsynchronous) { + return; + } + [self willChangeValueForKey:@"asynchronous"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _asynchronous = isAsynchronous; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"asynchronous"]; +} + +- (BOOL)isAsynchronous { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL asynchronous = _asynchronous; + dispatch_semaphore_signal(_statusSem); + return asynchronous; +} + +- (void)setReady:(BOOL)isReady { + if (_ready == isReady) { + return; + } + [self willChangeValueForKey:@"ready"]; + { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + _ready = isReady; + dispatch_semaphore_signal(_statusSem); + } + [self didChangeValueForKey:@"ready"]; +} + +- (BOOL)isReady { + dispatch_semaphore_wait(_statusSem, DISPATCH_TIME_FOREVER); + BOOL ready = _ready; + dispatch_semaphore_signal(_statusSem); + return ready; +} + +@end diff --git a/framework/ios/base/bundleoperations/HippyBundleLoadOperation.h b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.h new file mode 100644 index 00000000000..9d4e2a96449 --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.h @@ -0,0 +1,39 @@ +/*! + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@class HippyBridge; + +typedef void (^HippyBundleLoadCompletionBlock)(NSData *, NSError *); + +@interface HippyBundleLoadOperation : NSOperation + +@property(nonatomic, copy) HippyBundleLoadCompletionBlock onLoad; + +- (instancetype)initWithBridge:(HippyBridge *)bridge bundleURL:(NSURL *)bundleURL queue:(NSOperationQueue *)queue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm new file mode 100644 index 00000000000..02594550199 --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleLoadOperation.mm @@ -0,0 +1,207 @@ +/*! + * 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 "HippyBundleLoadOperation.h" +#import "HippyBridge+VFSLoader.h" + +#include + +@interface HippyBundleLoadOperation () { + HippyBridge *_bridge; + NSURL *_bundleURL; + + BOOL _cancelled; + BOOL _executing; + BOOL _finished; + BOOL _asynchronous; + BOOL _ready; + std::mutex _statusMutex; + NSOperationQueue *_finishQueue; +} + +@end + +@implementation HippyBundleLoadOperation + +@synthesize finished = _finished; +@synthesize executing = _executing; + +- (instancetype)initWithBridge:(HippyBridge *)bridge bundleURL:(NSURL *)bundleURL queue:(NSOperationQueue *)queue { + self = [super init]; + if (self) { + _bridge = bridge; + _bundleURL = bundleURL; + _finishQueue = queue; + self.ready = YES; + } + return self; +} + +- (void)start { + self.asynchronous = YES; + self.concurrent = YES; + [self main]; +} + +- (void)main { + if (self.cancelled) { + return; + } + self.finished = NO; + self.executing = YES; + HippyBridge *bridge = _bridge; + NSString *bundleURL = [_bundleURL absoluteString]; + __weak HippyBundleLoadOperation *weakSelf = self; + [bridge loadContentsAsynchronouslyFromUrl:bundleURL + method:@"get" + params:nil + body:nil + queue:_finishQueue + progress:nil + completionHandler:^(NSData * _Nullable data, + NSDictionary * _Nullable userInfo, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + HippyBundleLoadOperation *strongSelf = weakSelf; + if (!strongSelf || strongSelf.cancelled) { + strongSelf.finished = YES; + strongSelf.executing = NO; + return; + } + if (strongSelf.onLoad) { + strongSelf.onLoad(data, error); + } + strongSelf.finished = YES; + strongSelf.executing = NO; + }]; +} + +- (void)cancel { + self.cancelled = YES; +} + +- (void)setCancelled:(BOOL)cancelled { + if (_cancelled == cancelled) { + return; + } + [self willChangeValueForKey:@"isCancelled"]; + { + std::lock_guard lock(_statusMutex); + _cancelled = cancelled; + } + [self didChangeValueForKey:@"isCancelled"]; +} + +- (BOOL)isCancelled { + std::lock_guard lock(_statusMutex); + return _cancelled; +} + +- (void)setExecuting:(BOOL)isExecuting { + if (_executing == isExecuting) { + return; + } + [self willChangeValueForKey:@"isExecuting"]; + { + std::lock_guard lock(_statusMutex); + _executing = isExecuting; + } + [self didChangeValueForKey:@"isExecuting"]; +} + +- (BOOL)isExecuting { + std::lock_guard lock(_statusMutex); + return _executing; +} + +- (void)setFinished:(BOOL)isFinished { + if (_finished == isFinished) { + return; + } + [self willChangeValueForKey:@"isFinished"]; + { + std::lock_guard lock(_statusMutex); + _finished = isFinished; + } + [self didChangeValueForKey:@"isFinished"]; +} + +- (BOOL)isFinished { + return _finished; +} + +- (void)setConcurrent:(BOOL)isConcurrent { + if (_asynchronous == isConcurrent) { + return; + } + [self willChangeValueForKey:@"isConcurrent"]; + { + std::lock_guard lock(_statusMutex); + _asynchronous = isConcurrent; + } + [self didChangeValueForKey:@"isConcurrent"]; +} + +- (BOOL)isConcurrent { + std::lock_guard lock(_statusMutex); + return _asynchronous; +} + +- (void)setAsynchronous:(BOOL)isAsynchronous { + if (_asynchronous == isAsynchronous) { + return; + } + [self willChangeValueForKey:@"isAsynchronous"]; + { + std::lock_guard lock(_statusMutex); + _asynchronous = isAsynchronous; + } + [self didChangeValueForKey:@"isAsynchronous"]; +} + +- (BOOL)isAsynchronous { + std::lock_guard lock(_statusMutex); + return _asynchronous; +} + +- (void)setReady:(BOOL)isReady { + if (_ready == isReady) { + return; + } + [self willChangeValueForKey:@"isReady"]; + { + std::lock_guard lock(_statusMutex); + _ready = isReady; + } + [self didChangeValueForKey:@"isReady"]; +} + +- (BOOL)isReady { + std::lock_guard lock(_statusMutex); + return _ready; +} + +- (void)dealloc { + +} + +@end diff --git a/framework/ios/base/bundleoperations/HippyBundleOperationQueue.h b/framework/ios/base/bundleoperations/HippyBundleOperationQueue.h new file mode 100644 index 00000000000..b5ac962033d --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleOperationQueue.h @@ -0,0 +1,33 @@ +/*! + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface HippyBundleOperationQueue : NSObject + +- (void)addOperations:(NSArray *)ops; + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/base/bundleoperations/HippyBundleOperationQueue.m b/framework/ios/base/bundleoperations/HippyBundleOperationQueue.m new file mode 100644 index 00000000000..add8fe44120 --- /dev/null +++ b/framework/ios/base/bundleoperations/HippyBundleOperationQueue.m @@ -0,0 +1,87 @@ +/*! + * 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 "HippyBundleOperationQueue.h" + +@interface HippyBundleOperationQueue () { + NSMutableArray *_ops; +} + +@end + +@implementation HippyBundleOperationQueue + +- (instancetype)init { + self = [super init]; + if (self) { + _ops = [NSMutableArray array]; + } + return self; +} + +- (void)addOperations:(NSArray *)ops { + if (ops) { + for (NSOperation *op in ops) { + if ([op isReady]) { + [op addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:NULL]; + @synchronized (self) { + [_ops addObject:op]; + } + [op start]; + } else if ([op isCancelled]) { + // do nothing + } else { + [op addObserver:self forKeyPath:@"ready" options:NSKeyValueObservingOptionNew context:NULL]; + [op addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:NULL]; + @synchronized (self) { + [_ops addObject:op]; + } + } + } + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + NSNumber *value = [change objectForKey:NSKeyValueChangeNewKey]; + if (!value) { + return; + } + NSOperation *op = (NSOperation *)object; + if (![op isKindOfClass:[NSOperation class]]) { + return; + } + BOOL status = [value boolValue]; + if ([keyPath isEqualToString:@"ready"] && status) { + [op removeObserver:self forKeyPath:@"ready" context:NULL]; + [op start]; + } else if ([keyPath isEqualToString:@"finished"] && status) { + @synchronized (self) { + [_ops removeObject:object]; + } + [op removeObserver:self forKeyPath:@"finished" context:NULL]; + } +} + +@end