From ea6902efe863f477bdebeb2ad8ae5da4cb3e1d61 Mon Sep 17 00:00:00 2001 From: luomy Date: Fri, 4 Mar 2022 19:50:06 +0800 Subject: [PATCH] refactor(ios): image component refactor (#1591) * feat(ios): add hippy image data loader * feat(ios): add hippy image data loader protocol * feat(ios): add data request for path * refactor(ios): refactor image component data loader --- .../HippyDemo.xcodeproj/project.pbxproj | 12 +- ios/sdk/base/HippyBatchedBridge.mm | 4 +- .../image/HippyDefaultImageProvider.m | 24 +- .../component/image/HippyImageDataLoader.h | 37 ++ .../component/image/HippyImageDataLoader.m | 231 ++++++++++++ .../image/HippyImageDataLoaderProtocol.h | 43 +++ .../image/HippyImageProviderProtocol.h | 12 +- .../image/HippyImageProviderProtocol.m | 51 --- ios/sdk/component/image/HippyImageView.h | 2 +- ios/sdk/component/image/HippyImageView.m | 354 ++---------------- .../component/image/HippyImageViewManager.h | 6 + .../component/image/HippyImageViewManager.m | 123 +++++- .../image loader/HippyImageLoaderModule.m | 94 ++--- ios/sdk/utils/HippyUtils.m | 15 +- renderer/native/ios/HippyUIManager.mm | 28 +- 15 files changed, 535 insertions(+), 501 deletions(-) create mode 100644 ios/sdk/component/image/HippyImageDataLoader.h create mode 100644 ios/sdk/component/image/HippyImageDataLoader.m create mode 100644 ios/sdk/component/image/HippyImageDataLoaderProtocol.h delete mode 100644 ios/sdk/component/image/HippyImageProviderProtocol.m diff --git a/framework/js/examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj b/framework/js/examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj index c23e9ac8c..28f2b7704 100644 --- a/framework/js/examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj +++ b/framework/js/examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj @@ -123,7 +123,6 @@ 0691853226281E05005D599D /* CALayer+HippyAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0691853126281E05005D599D /* CALayer+HippyAnimation.m */; }; 06F2A76F2519E319000AF839 /* HippyJSEnginesMapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 06F2A76E2519E319000AF839 /* HippyJSEnginesMapper.mm */; }; 06F3E3E025A5D867007934F6 /* HippyListTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 06F3E3DF25A5D867007934F6 /* HippyListTableView.m */; }; - 06FF8BD62511BC0400C03900 /* HippyImageProviderProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 06FF8BD52511BC0400C03900 /* HippyImageProviderProtocol.m */; }; 06FF8EEC2511C10B00C03900 /* HippyDefaultImageProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 06FF8EEB2511C10B00C03900 /* HippyDefaultImageProvider.m */; }; 06FF8EEF2511C20900C03900 /* NSData+DataType.m in Sources */ = {isa = PBXBuildFile; fileRef = 06FF8EEE2511C20900C03900 /* NSData+DataType.m */; }; 781580042795449A006E92D4 /* HippyDevInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 7815800327954499006E92D4 /* HippyDevInfo.m */; }; @@ -206,6 +205,7 @@ F48291AA274E5BB600D91128 /* HippyOCTurboModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = F48291A7274E5BB500D91128 /* HippyOCTurboModule.mm */; }; F48291AB274E5BB600D91128 /* HippyTurboModuleManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = F48291A8274E5BB500D91128 /* HippyTurboModuleManager.mm */; }; F48291AC274E5BB600D91128 /* NSObject+HippyTurbo.mm in Sources */ = {isa = PBXBuildFile; fileRef = F48291A9274E5BB500D91128 /* NSObject+HippyTurbo.mm */; }; + F4833E6727CF24320054A33D /* HippyImageDataLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = F4833E6627CF24320054A33D /* HippyImageDataLoader.m */; }; F4AFCB1627BE29D50058AAB1 /* UIView+HippyEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4AFCB1527BE29D50058AAB1 /* UIView+HippyEvent.mm */; }; F4CB9D29279270D3008D6F2C /* UIView+RootViewRegister.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4CB9D28279270D3008D6F2C /* UIView+RootViewRegister.mm */; }; F4D0028F2675C7D5004A43B6 /* HippyGradientObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F4D0028E2675C7D5004A43B6 /* HippyGradientObject.m */; }; @@ -475,7 +475,6 @@ 06F3E3DE25A5D867007934F6 /* HippyListTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyListTableView.h; sourceTree = ""; }; 06F3E3DF25A5D867007934F6 /* HippyListTableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HippyListTableView.m; sourceTree = ""; }; 06FF8BD42511BC0400C03900 /* HippyImageProviderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyImageProviderProtocol.h; sourceTree = ""; }; - 06FF8BD52511BC0400C03900 /* HippyImageProviderProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HippyImageProviderProtocol.m; sourceTree = ""; }; 06FF8EEA2511C10B00C03900 /* HippyDefaultImageProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyDefaultImageProvider.h; sourceTree = ""; }; 06FF8EEB2511C10B00C03900 /* HippyDefaultImageProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HippyDefaultImageProvider.m; sourceTree = ""; }; 06FF8EED2511C20900C03900 /* NSData+DataType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+DataType.h"; sourceTree = ""; }; @@ -654,6 +653,9 @@ F48291A7274E5BB500D91128 /* HippyOCTurboModule.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HippyOCTurboModule.mm; sourceTree = ""; }; F48291A8274E5BB500D91128 /* HippyTurboModuleManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HippyTurboModuleManager.mm; sourceTree = ""; }; F48291A9274E5BB500D91128 /* NSObject+HippyTurbo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSObject+HippyTurbo.mm"; sourceTree = ""; }; + F4833E6527CF24320054A33D /* HippyImageDataLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyImageDataLoader.h; sourceTree = ""; }; + F4833E6627CF24320054A33D /* HippyImageDataLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HippyImageDataLoader.m; sourceTree = ""; }; + F4833E6827CF462C0054A33D /* HippyImageDataLoaderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyImageDataLoaderProtocol.h; sourceTree = ""; }; F4AFCB1427BE29D50058AAB1 /* UIView+HippyEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+HippyEvent.h"; sourceTree = ""; }; F4AFCB1527BE29D50058AAB1 /* UIView+HippyEvent.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIView+HippyEvent.mm"; sourceTree = ""; }; F4CB9D27279270D3008D6F2C /* UIView+RootViewRegister.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+RootViewRegister.h"; sourceTree = ""; }; @@ -1009,9 +1011,11 @@ 064C594323AB1A51001E80DD /* HippyAnimatedImageView.h */, 064C594723AB1A51001E80DD /* HippyAnimatedImageView.m */, 06FF8BD42511BC0400C03900 /* HippyImageProviderProtocol.h */, - 06FF8BD52511BC0400C03900 /* HippyImageProviderProtocol.m */, 06FF8EEA2511C10B00C03900 /* HippyDefaultImageProvider.h */, 06FF8EEB2511C10B00C03900 /* HippyDefaultImageProvider.m */, + F4833E6527CF24320054A33D /* HippyImageDataLoader.h */, + F4833E6627CF24320054A33D /* HippyImageDataLoader.m */, + F4833E6827CF462C0054A33D /* HippyImageDataLoaderProtocol.h */, ); path = image; sourceTree = ""; @@ -1909,7 +1913,6 @@ 781580042795449A006E92D4 /* HippyDevInfo.m in Sources */, 064C5A4A23AB1A51001E80DD /* HippyModuleMethod.mm in Sources */, D84AB9C6273A618B00987D03 /* thread.cc in Sources */, - 06FF8BD62511BC0400C03900 /* HippyImageProviderProtocol.m in Sources */, F4E7B43E276C61EB00F17139 /* dom_event.cc in Sources */, D84AB9BF273A618B00987D03 /* timer_module.cc in Sources */, D84AB9C5273A618B00987D03 /* task.cc in Sources */, @@ -1924,6 +1927,7 @@ 064C59F223AB1A51001E80DD /* HippyNetWork.m in Sources */, 064C5A0523AB1A51001E80DD /* HippyClipboardModule.m in Sources */, F45D1BDD26B1555E004010EE /* HippyI18nUtils.m in Sources */, + F4833E6727CF24320054A33D /* HippyImageDataLoader.m in Sources */, 787C199E2726A1020062EBFF /* HippyDomModel.mm in Sources */, 7882DFDC272929D300C12149 /* HippyCSSPropsDefine.m in Sources */, 064C5A2A23AB1A51001E80DD /* HippyBackgroundImageCacheManager.m in Sources */, diff --git a/ios/sdk/base/HippyBatchedBridge.mm b/ios/sdk/base/HippyBatchedBridge.mm index 154179acd..6e0b4b613 100644 --- a/ios/sdk/base/HippyBatchedBridge.mm +++ b/ios/sdk/base/HippyBatchedBridge.mm @@ -783,7 +783,9 @@ HIPPY_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL if (!_valid) { return; } - _domManager->TerminateTaskRunner(); + if (_domManager) { + _domManager->TerminateTaskRunner(); + } HippyAssertMainQueue(); HippyAssert(_javaScriptExecutor != nil, @"Can't complete invalidation without a JS executor"); diff --git a/ios/sdk/component/image/HippyDefaultImageProvider.m b/ios/sdk/component/image/HippyDefaultImageProvider.m index 0232032f2..e30486423 100644 --- a/ios/sdk/component/image/HippyDefaultImageProvider.m +++ b/ios/sdk/component/image/HippyDefaultImageProvider.m @@ -35,6 +35,8 @@ @implementation HippyDefaultImageProvider +@synthesize imageDataPath; + HIPPY_EXPORT_MODULE(defaultImageProvider) + (BOOL)canHandleData:(NSData *)data { @@ -46,26 +48,22 @@ HIPPY_EXPORT_MODULE(defaultImageProvider) return ret; } -+ (NSUInteger)priorityForData:(NSData *)data { - return 0; -} - -+ (instancetype)imageProviderInstanceForData:(NSData *)data { - return [[[self class] alloc] initWithData:data]; -} - - (instancetype)initWithData:(NSData *)data { self = [super init]; if (self) { - if ([[self class] isAnimatedImage:data]) { - _imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); - } else { - _data = data; - } + [self setImageData:data]; } return self; } +- (void)setImageData:(NSData *)imageData { + if ([[self class] isAnimatedImage:imageData]) { + _imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); + } else { + _data = imageData; + } +} + - (UIImage *)image { if (nil == _image) { if (_data) { diff --git a/ios/sdk/component/image/HippyImageDataLoader.h b/ios/sdk/component/image/HippyImageDataLoader.h new file mode 100644 index 000000000..0b1f9b30d --- /dev/null +++ b/ios/sdk/component/image/HippyImageDataLoader.h @@ -0,0 +1,37 @@ +/*! + * 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 +#import "HippyImageDataLoaderProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const HippyImageDataLoaderErrorDomain; +extern const NSUInteger HippyImageDataLoaderErrorFileNotExists; +extern const NSUInteger HippyImageDataLoaderErrorNoPathIncoming; +extern const NSUInteger HippyImageDataLoaderErrorPathToUrl; + +@interface HippyImageDataLoader : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/sdk/component/image/HippyImageDataLoader.m b/ios/sdk/component/image/HippyImageDataLoader.m new file mode 100644 index 000000000..833b04240 --- /dev/null +++ b/ios/sdk/component/image/HippyImageDataLoader.m @@ -0,0 +1,231 @@ +/*! + * 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 "HippyImageDataLoader.h" +#import "HippyUtils.h" +#import "HippyImageCacheManager.h" + +NSString *const HippyImageDataLoaderErrorDomain = @"HippyImageDataLoaderErrorDomain"; + +const NSUInteger HippyImageDataLoaderErrorFileNotExists = 1000; +const NSUInteger HippyImageDataLoaderErrorNoPathIncoming = 1001; +const NSUInteger HippyImageDataLoaderErrorPathToUrl = 1002; + +typedef NS_ENUM(NSUInteger, ImagePathType) { + ImagePathTypeUnknown, + ImagePathTypeHTTPPath, + ImagePathTypeFilePath, + ImagePathTypeBase64Path, +}; + +typedef void(^DownloadProgress)(NSUInteger, NSUInteger); +typedef void(^CompletionBlock)(id, NSString *, NSError *); + +static ImagePathType checkPathTypeFromPath(NSString *path) { + ImagePathType type = ImagePathTypeUnknown; + if ([path hasPrefix:@"http"]) { + type = ImagePathTypeHTTPPath; + } + else if ([path hasPrefix:@"file://"]) { + type = ImagePathTypeFilePath; + } + else if ([path containsString:@"data:image/"] && [path containsString:@";base64,"]) { + type = ImagePathTypeBase64Path; + } + return type; +} + +static NSOperationQueue *ImageDataLoaderQueue(void) { + static dispatch_once_t onceToken; + static NSOperationQueue *_hippy_image_queue = nil; + dispatch_once(&onceToken, ^{ + _hippy_image_queue = [[NSOperationQueue alloc] init]; + _hippy_image_queue.maxConcurrentOperationCount = 1; + }); + return _hippy_image_queue; +} + +@interface HippyImageDataLoader () { + ImagePathType _imagePathType; + NSURLSessionDataTask *_task; + NSMutableData *_imageData; + NSUInteger _imageDataTotalLength; + DownloadProgress _progress; + CompletionBlock _completion; +} + +@end + +@implementation HippyImageDataLoader + +#pragma mark HippyImageDataLoaderProtocol Implementation + +- (BOOL)canHandleImageAtPath:(NSString *)path { + ImagePathType type = checkPathTypeFromPath(path); + return ImagePathTypeUnknown != type; +} + +- (void)loadImageAtPath:(NSString *)path progress:(DownloadProgress)progress + completion:(CompletionBlock)dataCompletion { + ImagePathType type = checkPathTypeFromPath(path); + switch (type) { + case ImagePathTypeHTTPPath: + [self loadImageAtHTTPPath:path progress:progress dataCompletion:dataCompletion]; + break; + case ImagePathTypeFilePath: + [self loadImageAtFilePath:path progress:progress dataCompletion:dataCompletion]; + break; + case ImagePathTypeBase64Path: + [self loadImageAtBase64Path:path progress:progress dataCompletion:dataCompletion]; + break; + default: + break; + } +} + +- (void)cancelImageDownloadAtPath:(NSString *)path { + [_task cancel]; +} + +#pragma mark Image Data Loader + +- (void)loadImageAtHTTPPath:(NSString *)path progress:(DownloadProgress)progress dataCompletion:(CompletionBlock)dataCompletion { + if (_task) { + [_task cancel]; + } + NSURL *sourceUrl = HippyURLWithString(path, nil); + if (sourceUrl) { + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self + delegateQueue:ImageDataLoaderQueue()]; + _task = [session dataTaskWithURL:sourceUrl]; + [_task resume]; + _progress = progress; + _completion = dataCompletion; + } +} + +- (void)loadImageAtFilePath:(NSString *)path progress:(DownloadProgress)progress dataCompletion:(CompletionBlock)dataCompletion { + if (path) { + BOOL isDirectory = NO; + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; + if (fileExists) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error]; + if (error) { + dataCompletion(nil, path, error); + } + else { + NSUInteger length = [data length]; + if (progress) { + progress(length, length); + } + if (dataCompletion) { + dataCompletion(data, path, nil); + } + } + }else { + if (dataCompletion) { + NSError *error = [NSError errorWithDomain:HippyImageDataLoaderErrorDomain code:HippyImageDataLoaderErrorFileNotExists userInfo:@{@"path": path}]; + dataCompletion(nil, path, error); + } + } + }else { + if (dataCompletion) { + NSError *error = [NSError errorWithDomain:HippyImageDataLoaderErrorDomain code:HippyImageDataLoaderErrorNoPathIncoming userInfo:nil]; + dataCompletion(nil, path, error); + } + } +} + +- (void)loadImageAtBase64Path:(NSString *)path progress:(DownloadProgress)progress dataCompletion:(CompletionBlock)dataCompletion { + if (path) { + NSURL *url = [NSURL URLWithString:path]; + if (url) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error]; + if (error) { + dataCompletion(nil, path, error); + }else { + NSUInteger length = [data length]; + if (progress) { + progress(length, length); + } + if (dataCompletion) { + dataCompletion(data, path, nil); + } + } + }else { + if (dataCompletion) { + NSError *error = [NSError errorWithDomain:HippyImageDataLoaderErrorDomain code:HippyImageDataLoaderErrorNoPathIncoming userInfo:nil]; + dataCompletion(nil, path, error); + } + } + }else { + if (dataCompletion) { + NSError *error = [NSError errorWithDomain:HippyImageDataLoaderErrorDomain code:HippyImageDataLoaderErrorNoPathIncoming userInfo:nil]; + dataCompletion(nil, path, error); + } + } +} + +#pragma mark URL request delegate implementation +- (void)URLSession:(__unused NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + if (_task == dataTask) { + _imageDataTotalLength = response.expectedContentLength; + completionHandler(NSURLSessionResponseAllow); + NSUInteger capacity = + NSURLResponseUnknownLength != _imageDataTotalLength ? (NSUInteger)_imageDataTotalLength : 256; + _imageData = [[NSMutableData alloc] initWithCapacity:capacity]; + } +} + +- (void)URLSession:(__unused NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + if (_task == dataTask) { + [_imageData appendData:data]; + if (_progress) { + _progress([_imageData length], _imageDataTotalLength); + } + } +} + +- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { + if (_task == task) { + if (_progress) { + _progress([_imageData length], _imageDataTotalLength); + } + if (_completion) { + _completion(_imageData, [[[task originalRequest] URL] absoluteString], error); + } + } + [session finishTasksAndInvalidate]; +} + +- (void)dealloc { + [_task cancel]; +} + +@end diff --git a/ios/sdk/component/image/HippyImageDataLoaderProtocol.h b/ios/sdk/component/image/HippyImageDataLoaderProtocol.h new file mode 100644 index 000000000..e4090971d --- /dev/null +++ b/ios/sdk/component/image/HippyImageDataLoaderProtocol.h @@ -0,0 +1,43 @@ +/*! + * 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol HippyImageDataLoaderProtocol + +@optional + +- (BOOL)canHandleImageAtPath:(NSString *)path; + +@required + +- (void)loadImageAtPath:(NSString *)path progress:(void (^)(NSUInteger, NSUInteger))progress + completion:(void (^)(id, NSString *, NSError *))completion; + +- (void)cancelImageDownloadAtPath:(NSString *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/sdk/component/image/HippyImageProviderProtocol.h b/ios/sdk/component/image/HippyImageProviderProtocol.h index afd5533e6..3f6495c5e 100644 --- a/ios/sdk/component/image/HippyImageProviderProtocol.h +++ b/ios/sdk/component/image/HippyImageProviderProtocol.h @@ -37,14 +37,14 @@ */ + (BOOL)isAnimatedImage:(NSData *)data; -/** return priority for data - * instance with higher priority will process data - */ -+ (NSUInteger)priorityForData:(NSData *)data; +@property(nonatomic, copy)NSString *imageDataPath; -/** return image provider instance for data +/** + * Set image data for provider + * + * @param data Image data */ -+ (instancetype)imageProviderInstanceForData:(NSData *)data; +- (void)setImageData:(NSData *)data; /** return image.If it is an animated image,return first frame */ diff --git a/ios/sdk/component/image/HippyImageProviderProtocol.m b/ios/sdk/component/image/HippyImageProviderProtocol.m deleted file mode 100644 index 21344f59c..000000000 --- a/ios/sdk/component/image/HippyImageProviderProtocol.m +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * 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 "HippyImageProviderProtocol.h" -#import "objc/runtime.h" -#import "HippyBridge.h" - -Class imageProviderClassFromBridge(NSData *data, HippyBridge *bridge) { - NSSet> *classes = [bridge imageProviders]; - NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary *_Nullable bindings) { - if ([evaluatedObject conformsToProtocol:@protocol(HippyImageProviderProtocol)]) { - Class class = (Class)evaluatedObject; - return [class canHandleData:data]; - } else { - return NO; - } - }]; - NSSet> *sub = [classes filteredSetUsingPredicate:predicate]; - Class candidate = nil; - for (Class class in sub) { - if (nil == candidate) { - candidate = class; - } else { - NSUInteger candidatePriority = [candidate priorityForData:data]; - NSUInteger classPriority = [class priorityForData:data]; - if (classPriority > candidatePriority) { - candidate = class; - } - } - } - return candidate; -} diff --git a/ios/sdk/component/image/HippyImageView.h b/ios/sdk/component/image/HippyImageView.h index eabd811ac..019fb32a8 100644 --- a/ios/sdk/component/image/HippyImageView.h +++ b/ios/sdk/component/image/HippyImageView.h @@ -78,7 +78,7 @@ typedef NS_ENUM(NSInteger, HippyResizeMode) { - (void)updateImage:(UIImage *)image; -- (UIImage *)imageFromData:(NSData *)data; +- (void)setImageProvider:(id)imageProvider; - (void)clearImageIfDetached; diff --git a/ios/sdk/component/image/HippyImageView.m b/ios/sdk/component/image/HippyImageView.m index 03711cdf9..3eaa4f25e 100644 --- a/ios/sdk/component/image/HippyImageView.m +++ b/ios/sdk/component/image/HippyImageView.m @@ -25,7 +25,6 @@ #import "HippyUtils.h" #import "HippyImageViewCustomLoader.h" #import "HippyBridge+LocalFileSource.h" -#import "HippyImageCacheManager.h" #import "HippyAnimatedImage.h" #import "HippyDefaultImageProvider.h" #import @@ -46,16 +45,6 @@ typedef struct _BorderRadiusStruct { CGFloat bottomRightRadius; } BorderRadiusStruct; -static NSOperationQueue *hippy_image_queue() { - static dispatch_once_t onceToken; - static NSOperationQueue *_hippy_image_queue = nil; - dispatch_once(&onceToken, ^{ - _hippy_image_queue = [[NSOperationQueue alloc] init]; - _hippy_image_queue.maxConcurrentOperationCount = 1; - }); - return _hippy_image_queue; -} - static NSOperationQueue *animated_image_queue() { static dispatch_once_t onceToken; static NSOperationQueue *_animatedImageOQ = nil; @@ -197,15 +186,12 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { @end @interface HippyImageView () { - NSURLSessionDataTask *_task; - NSURL *_imageLoadURL; - long long _totalLength; - NSMutableData *_data; __weak CALayer *_borderWidthLayer; BOOL _needsUpdateBorderRadius; CGSize _size; BOOL _needsReloadImage; BOOL _needsUpdateImage; + id _imageProvider; } @property (nonatomic) HippyAnimatedImageOperation *animatedImageOperation; @@ -236,7 +222,6 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { - (void)didMoveToWindow { [super didMoveToWindow]; if (!self.window) { - [self cancelImageLoad]; } else if ([self shouldChangeImageSource]) { [self reloadImage]; } @@ -309,7 +294,6 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { - (void)setResizeMode:(HippyResizeMode)resizeMode { if (_resizeMode != resizeMode) { _resizeMode = resizeMode; - if (_resizeMode == HippyResizeModeRepeat) { self.contentMode = UIViewContentModeScaleToFill; } else { @@ -328,6 +312,12 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { } } +- (void)setImageProvider:(id)imageProvider { + if (_imageProvider != imageProvider) { + _imageProvider = imageProvider; + } +} + - (void)didSetProps:(NSArray *)changedProps { if (_needsReloadImage) { self.animatedImage = nil; @@ -354,311 +344,40 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { } - (void)reloadImage { - NSDictionary *source = [self.source firstObject]; - if (source && CGRectGetWidth(self.frame) > 0 && CGRectGetHeight(self.frame) > 0) { - if (_onLoadStart) { - _onLoadStart(@{}); - } - NSString *uri = source[@"uri"]; - self.pendingImageSourceUri = uri; - BOOL isBlurredImage = NO; - NSData *data = [[HippyImageCacheManager sharedInstance] imageCacheDataForURLString:uri]; - if (data) { - Class ipClass = imageProviderClassFromBridge(data, self.bridge); - id instance = [ipClass imageProviderInstanceForData:data]; - if (instance) { - BOOL isAnimatedImage = [instance imageCount] > 1; - if (isAnimatedImage) { - if (_animatedImageOperation) { - [_animatedImageOperation cancel]; - } - _animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:uri]; - [animated_image_queue() addOperation:_animatedImageOperation]; - } else { - UIImage *image = [instance image]; - if (image) { - [self loadImage:image url:uri error:nil needBlur:!isBlurredImage needCache:YES]; - } else { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable for uri %@", uri]; - NSError *theError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:uri error:theError needBlur:YES needCache:NO]; - } - } - return; + if (_imageProvider) { + NSString *imagePath = _imageProvider.imageDataPath; + BOOL isAnimatedImage = [_imageProvider imageCount] > 1; + if (isAnimatedImage) { + if (_animatedImageOperation) { + [_animatedImageOperation cancel]; } - } - - if ([uri hasPrefix:@"file://"]) { - NSURL *fileURL = [NSURL URLWithString:uri]; - NSError *error = nil; - NSData *imageData = [NSData dataWithContentsOfURL:fileURL options:(NSDataReadingMappedIfSafe) error:&error]; - if (error) { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable for uri %@", uri]; + _animatedImageOperation = + [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:_imageProvider + imageView:self + imageURL:imagePath]; + [animated_image_queue() addOperation:_animatedImageOperation]; + }else { + UIImage *image = [_imageProvider image]; + if (image) { + [self loadImage:image url:imagePath error:nil needBlur:YES]; + } else { + NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable for uri %@", _imageProvider.imageDataPath]; NSError *theError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:uri error:theError needBlur:YES needCache:NO]; - } - else { - Class ipClass = imageProviderClassFromBridge(imageData, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:imageData]; - BOOL isAnimatedImage = [ipClass isAnimatedImage:imageData]; - if (isAnimatedImage) { - if (_animatedImageOperation) { - [_animatedImageOperation cancel]; - } - _animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:source[@"uri"]]; - [animated_image_queue() addOperation:_animatedImageOperation]; - } else { - UIImage *image = [instance image]; - if (image) { - [self loadImage:image url:uri error:nil needBlur:YES needCache:YES]; - } else { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable for uri %@", uri]; - NSError *theError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:uri error:theError needBlur:YES needCache:NO]; - } - } - } - return; - } - - NSData *uriData = [uri dataUsingEncoding:NSUTF8StringEncoding]; - if (nil == uriData) { - return; - } - CFURLRef urlRef = CFURLCreateWithBytes(NULL, [uriData bytes], [uriData length], kCFStringEncodingUTF8, NULL); - NSURL *source_url = CFBridgingRelease(urlRef); - if ([HippyBridge isHippyLocalFileURLString:uri]) { - NSString *localPath = [_bridge absoluteStringFromHippyLocalFileURLString:uri]; - BOOL isDirectory = NO; - BOOL fileExist = [[NSFileManager defaultManager] fileExistsAtPath:localPath isDirectory:&isDirectory]; - if (fileExist && !isDirectory) { - NSData *imageData = [NSData dataWithContentsOfFile:localPath]; - Class ipClass = imageProviderClassFromBridge(imageData, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:imageData]; - BOOL isAnimatedImage = [ipClass isAnimatedImage:imageData]; - if (isAnimatedImage) { - if (_animatedImageOperation) { - [_animatedImageOperation cancel]; - } - _animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:source[@"uri"]]; - [animated_image_queue() addOperation:_animatedImageOperation]; - } else { - UIImage *image = [instance image]; - if (image) { - [self loadImage:image url:source_url.absoluteString error:nil needBlur:YES needCache:YES]; - } else { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable for uri %@", source_url.absoluteString]; - NSError *theError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:source_url.absoluteString error:theError needBlur:YES needCache:NO]; - } - } - } else { - NSString *errorMessage = [NSString stringWithFormat:@"local image data not exist %@", source_url.absoluteString]; - NSError *error = imageErrorFromParams(ImageDataNotExist, errorMessage); - [self loadImage:nil url:source_url.absoluteString error:error needBlur:YES needCache:NO]; - } - return; - } - - __weak __typeof(self) weakSelf = self; - - typedef void (^HandleBase64CompletedBlock)(NSString *); - HandleBase64CompletedBlock handleBase64CompletedBlock = ^void(NSString *base64Data) { - NSRange range = [base64Data rangeOfString:@";base64,"]; - if (NSNotFound != range.location) { - base64Data = [base64Data substringFromIndex:range.location + range.length]; - NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; - Class ipClass = imageProviderClassFromBridge(imageData, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:imageData]; - BOOL isAnimatedImage = [ipClass isAnimatedImage:imageData]; - if (isAnimatedImage) { - if (weakSelf.animatedImageOperation) { - [weakSelf.animatedImageOperation cancel]; - } - weakSelf.animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:source[@"uri"]]; - [animated_image_queue() addOperation:weakSelf.animatedImageOperation]; - } else { - UIImage *image = [instance image]; - if (image) { - [weakSelf loadImage:image url:source[@"uri"] error:nil needBlur:YES needCache:YES]; - } else { - NSError *error = imageErrorFromParams(ImageDataUnavailable, @"base64 data not available"); - [self loadImage:nil url:source[@"uri"] error:error needBlur:YES needCache:NO]; - } - } - } - }; - - typedef void (^HandleImageCompletedBlock)(NSURL *); - HandleImageCompletedBlock handleImageCompletedBlock = ^void(NSURL *source_url) { - [weakSelf.bridge.imageLoader imageView:weakSelf loadAtUrl:source_url placeholderImage:weakSelf.defaultImage context:NULL - progress:^(long long currentLength, long long totalLength) { - if (weakSelf.onProgress) { - weakSelf.onProgress(@{@"loaded": @((double)currentLength), @"total": @((double)totalLength)}); - } - } - completed:^(NSData *data, NSURL *url, NSError *error) { - Class ipClass = imageProviderClassFromBridge(data, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:data]; - BOOL isAnimatedImage = [ipClass isAnimatedImage:data]; - if (isAnimatedImage) { - if (weakSelf.animatedImageOperation) { - [weakSelf.animatedImageOperation cancel]; - } - weakSelf.animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:source[@"uri"]]; - [animated_image_queue() addOperation:weakSelf.animatedImageOperation]; - } else { - UIImage *image = [instance image]; - if (image) { - [weakSelf loadImage:image url:source[@"uri"] error:nil needBlur:YES needCache:YES]; - } else { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable %@", source[@"uri"]]; - NSError *error = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [weakSelf loadImage:nil url:source[@"uri"] error:error needBlur:YES needCache:NO]; - } - } - }]; - }; - - if (_bridge.imageLoader && source_url) { - if (_defaultImage) { - weakSelf.image = _defaultImage; - } - - if ([[source_url absoluteString] hasPrefix:@"data:image/"]) { - handleBase64CompletedBlock([source_url absoluteString]); - } else { - if (_imageLoadURL) { - [_bridge.imageLoader cancelImageDownload:self withUrl:_imageLoadURL]; - } - _imageLoadURL = source_url; - handleImageCompletedBlock(source_url); - } - - } else { - if ([uri hasPrefix:@"data:image/"]) { - handleBase64CompletedBlock(uri); - } else { - if (_task) { - [self cancelImageLoad]; - } - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:hippy_image_queue()]; - _task = [session dataTaskWithURL:source_url]; - [_task resume]; + [self loadImage:nil url:imagePath error:theError needBlur:YES]; } } } } -- (void)cancelImageLoad { - self.pendingImageSourceUri = nil; - NSDictionary *source = [self.source firstObject]; - if (_bridge.imageLoader) { - [_animatedImageOperation cancel]; - _animatedImageOperation = nil; - [_bridge.imageLoader cancelImageDownload:self withUrl:source[@"uri"]]; - } else { - [_task cancel]; - _task = nil; - [hippy_image_queue() addOperationWithBlock:^{ - self->_data = nil; - self->_totalLength = 0; - }]; - } -} - - (void)clearImage { - [self cancelImageLoad]; [self.layer removeAnimationForKey:@"contents"]; self.image = nil; self.imageSourceUri = nil; } -- (UIImage *)imageFromData:(NSData *)data { - if (nil == data) { - return nil; - } - Class ipClass = imageProviderClassFromBridge(data, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:data]; - return [instance image]; -} - -#pragma mark - -- (void)URLSession:(__unused NSURLSession *)session - dataTask:(__unused NSURLSessionDataTask *)dataTask - didReceiveResponse:(NSURLResponse *)response - completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { - if (_task == dataTask) { - _totalLength = response.expectedContentLength; - completionHandler(NSURLSessionResponseAllow); - NSUInteger capacity = NSURLResponseUnknownLength != _totalLength ? (NSUInteger)_totalLength : 256; - _data = [[NSMutableData alloc] initWithCapacity:capacity]; - } -} - -- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - if (_task == dataTask) { - if (_onProgress && NSURLResponseUnknownLength != _totalLength) { - _onProgress(@{ @"loaded": @((double)data.length), @"total": @((double)_totalLength) }); - } - [_data appendData:data]; - } -} - -- (void)URLSession:(__unused NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { - if (_task == task) { - NSString *urlString = [[[task originalRequest] URL] absoluteString]; - if (!error) { - if ([_data length] > 0) { - Class ipClass = imageProviderClassFromBridge(_data, self.bridge); - id instance = [self instanceImageProviderFromClass:ipClass imageData:_data]; - BOOL isAnimatedImage = [ipClass isAnimatedImage:_data]; - if (isAnimatedImage) { - if (_animatedImageOperation) { - [_animatedImageOperation cancel]; - } - _animatedImageOperation = [[HippyAnimatedImageOperation alloc] initWithAnimatedImageProvider:instance imageView:self - imageURL:urlString]; - [animated_image_queue() addOperation:_animatedImageOperation]; - } else { - UIImage *image = [self imageFromData:_data]; - ; - if (image) { - [[HippyImageCacheManager sharedInstance] setImageCacheData:_data forURLString:urlString]; - [self loadImage:image url:urlString error:nil needBlur:YES needCache:YES]; - } else { - NSString *errorMessage = [NSString stringWithFormat:@"image data unavailable %@", urlString]; - NSError *theError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:urlString error:theError needBlur:YES needCache:NO]; - } - } - } else { - NSURLResponse *response = [task response]; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - NSUInteger statusCode = [httpResponse statusCode]; - NSString *errorMessage = [NSString stringWithFormat:@"no data received, HTTPStatusCode is %zd, url is %@", statusCode, urlString]; - NSError *imgError = imageErrorFromParams(ImageDataUnavailable, errorMessage); - [self loadImage:nil url:urlString error:imgError needBlur:NO needCache:NO]; - } - } - } else { - NSError *imgError = imageErrorFromParams(ImageDataReceivedError, error.localizedDescription); - [self loadImage:nil url:urlString error:imgError needBlur:YES needCache:NO]; - } - } - [session finishTasksAndInvalidate]; -} - #pragma mark - -- (void)loadImage:(UIImage *)image url:(NSString *)url error:(NSError *)error needBlur:(BOOL)needBlur needCache:(BOOL)needCache { +- (void)loadImage:(UIImage *)image url:(NSString *)url error:(NSError *)error needBlur:(BOOL)needBlur { if (error) { if (_onError && error.code != NSURLErrorCancelled) { _onError(@{ @"error": error.localizedDescription, @"errorCode": @(error.code), @"errorURL": url }); @@ -668,8 +387,7 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { } return; } - NSDictionary *source = [self.source firstObject]; - NSString *currentImageURLString = [source objectForKey:@"uri"]; + NSString *currentImageURLString = _imageProvider.imageDataPath; BOOL shouldContinue = url && currentImageURLString && [url isEqualToString:currentImageURLString]; if (!shouldContinue) { return; @@ -705,18 +423,12 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { NSError *finalError = HippyErrorFromErrorAndModuleName(error, self.bridge.moduleName); HippyFatal(finalError); } - if (needCache) { - [[HippyImageCacheManager sharedInstance] setImage:blurredImage forURLString:url blurRadius:br]; - } HippyExecuteOnMainQueue(^{ setImageBlock(blurredImage); }); }); } else { HippyExecuteOnMainQueue(^{ - if (needCache) { - [[HippyImageCacheManager sharedInstance] setImage:image forURLString:url blurRadius:br]; - } setImageBlock(image); }); } @@ -885,16 +597,6 @@ NSError *imageErrorFromParams(NSInteger errorCode, NSString *errorDescription) { return radius; } -- (id)instanceImageProviderFromClass:(Class)cls imageData:(NSData *)data { - id instance = [cls imageProviderInstanceForData:data]; - if ([instance isKindOfClass:[HippyDefaultImageProvider class]]) { - HippyDefaultImageProvider *provider = (HippyDefaultImageProvider *)instance; - provider.imageViewSize = _size; - provider.downSample = _downSample; - } - return instance; -} - @end @implementation UIImage (Hippy) @@ -958,7 +660,7 @@ HIPPY_ENUM_CONVERTER(HippyResizeMode, (@{ dispatch_async(dispatch_get_main_queue(), ^{ HippyImageView *sIV = wIV; NSString *sURL = wURL; - [sIV loadImage:animatedImage.posterImage url:sURL error:nil needBlur:YES needCache:NO]; + [sIV loadImage:animatedImage.posterImage url:sURL error:nil needBlur:YES]; sIV.animatedImage = animatedImage; }); } diff --git a/ios/sdk/component/image/HippyImageViewManager.h b/ios/sdk/component/image/HippyImageViewManager.h index 48806f0c6..c44406a14 100644 --- a/ios/sdk/component/image/HippyImageViewManager.h +++ b/ios/sdk/component/image/HippyImageViewManager.h @@ -21,7 +21,13 @@ */ #import "HippyViewManager.h" +#import "HippyImageDataLoaderProtocol.h" +#import "HippyImageProviderProtocol.h" @interface HippyImageViewManager : HippyViewManager +@property(nonatomic, strong)Class imageDataLoaderClass; + +@property(nonatomic, strong)Class imageProviderClass; + @end diff --git a/ios/sdk/component/image/HippyImageViewManager.m b/ios/sdk/component/image/HippyImageViewManager.m index 0cedbf51e..0946407b1 100644 --- a/ios/sdk/component/image/HippyImageViewManager.m +++ b/ios/sdk/component/image/HippyImageViewManager.m @@ -23,9 +23,17 @@ #import "HippyImageViewManager.h" #import "HippyImageView.h" #import "HippyConvert.h" -#import "HippyBridge+LocalFileSource.h" +#import "HippyImageDataLoader.h" +#import "HippyDefaultImageProvider.h" +#import "objc/runtime.h" #import +@interface HippyImageViewManager () { + NSMapTable> *_imageViewLoaderMapTable; +} + +@end + @implementation HippyImageViewManager HIPPY_EXPORT_MODULE(Image) @@ -33,7 +41,6 @@ HIPPY_EXPORT_MODULE(Image) HIPPY_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat) HIPPY_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) HIPPY_EXPORT_VIEW_PROPERTY(resizeMode, HippyResizeMode) -HIPPY_EXPORT_VIEW_PROPERTY(source, NSArray) HIPPY_EXPORT_VIEW_PROPERTY(onLoadStart, HippyDirectEventBlock) HIPPY_EXPORT_VIEW_PROPERTY(onProgress, HippyDirectEventBlock) HIPPY_EXPORT_VIEW_PROPERTY(onError, HippyDirectEventBlock) @@ -42,6 +49,37 @@ HIPPY_EXPORT_VIEW_PROPERTY(onLoad, HippyDirectEventBlock) HIPPY_EXPORT_VIEW_PROPERTY(onLoadEnd, HippyDirectEventBlock) HIPPY_EXPORT_VIEW_PROPERTY(downSample, BOOL) +HIPPY_CUSTOM_VIEW_PROPERTY(source, NSArray, HippyImageView) { + NSArray *pathSources = [HippyConvert NSArray:json]; + if ([pathSources isKindOfClass:[NSArray class]]) { + NSDictionary *dicSource = [pathSources firstObject]; + NSString *path = dicSource[@"uri"]; + id imageDataLoader = [self imageDataLoaderForView:view]; + __weak HippyImageView *weakView = view; + [imageDataLoader loadImageAtPath:path progress:^(NSUInteger current, NSUInteger total) { + } completion:^(id result, NSString *path, NSError *error) { + if (!error && weakView) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (weakView) { + HippyImageView *strongView = weakView; + if ([result isKindOfClass:[UIImage class]]) { + [strongView updateImage:(UIImage *)result]; + } + else if ([result isKindOfClass:[NSData class]]) { + Class cls = [self imageProviderClass]; + id imageProvider = [[cls alloc] init]; + imageProvider.imageDataPath = path; + [imageProvider setImageData:(NSData *)result]; + [strongView setImageProvider:imageProvider]; + [strongView reloadImage]; + } + } + }); + } + }]; + } +} + HIPPY_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, HippyImageView) { view.tintColor = [HippyConvert UIColor:json] ?: defaultView.tintColor; view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode; @@ -49,25 +87,27 @@ HIPPY_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, HippyImageView) { HIPPY_CUSTOM_VIEW_PROPERTY(defaultSource, NSString, HippyImageView) { NSString *source = [HippyConvert NSString:json]; - if ([HippyBridge isHippyLocalFileURLString:source]) { - NSString *localPath = [self.bridge absoluteStringFromHippyLocalFileURLString:source]; - BOOL isDirectory = NO; - BOOL fileExist = [[NSFileManager defaultManager] fileExistsAtPath:localPath isDirectory:&isDirectory]; - if (fileExist && !isDirectory) { - NSData *imageData = [NSData dataWithContentsOfFile:localPath]; - UIImage *image = [view imageFromData:imageData]; - view.defaultImage = image; + id imageDataLoader = [self imageDataLoaderForView:view]; + __weak HippyImageView *weakView = view; + [imageDataLoader loadImageAtPath:source progress:^(NSUInteger current, NSUInteger total) { + } completion:^(id result, NSString *path, NSError *error) { + if (!error && weakView) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (weakView) { + HippyImageView *strongView = weakView; + if ([result isKindOfClass:[NSData class]]) { + Class cls = [self imageProviderClass]; + id imageProvider = [[cls alloc] init]; + [imageProvider setImageData:(NSData *)result]; + strongView.defaultImage = [imageProvider image]; + } + else if ([result isKindOfClass:[UIImage class]]) { + strongView.defaultImage = (UIImage *)result; + } + } + }); } - } - else if ([source hasPrefix:@"data:image/"]) { - NSRange range = [source rangeOfString:@";base64,"]; - if (NSNotFound != range.location) { - source = [source substringFromIndex:range.location + range.length]; - } - NSData *imageData = [[NSData alloc] initWithBase64EncodedString:source options:NSDataBase64DecodingIgnoreUnknownCharacters]; - UIImage *image = [view imageFromData:imageData]; - view.defaultImage = image; - } + }]; } #define HIPPY_VIEW_BORDER_RADIUS_PROPERTY(SIDE) \ @@ -86,4 +126,47 @@ HIPPY_VIEW_BORDER_RADIUS_PROPERTY(BottomRight) return [[HippyImageView alloc] initWithBridge:self.bridge]; } +- (Class)imageDataLoaderClass { + if (!_imageDataLoaderClass) { + _imageDataLoaderClass = [HippyImageDataLoader class]; + } + return _imageDataLoaderClass; +} + +- (Class)imageProviderClass { + if (!_imageProviderClass) { + _imageProviderClass = [HippyDefaultImageProvider class]; + } + return _imageProviderClass; +} + +- (id)getNewImageDataLoaderInstance { + Class cls = [self imageDataLoaderClass]; + return [[cls alloc] init]; +} + +- (id)getNewImageProviderInstance { + Class cls = [self imageProviderClass]; + return [[cls alloc] init]; +} + +- (NSMapTable *)imageViewLoaderMapTable { + if (_imageViewLoaderMapTable) { + _imageViewLoaderMapTable = [NSMapTable weakToStrongObjectsMapTable]; + } + return _imageViewLoaderMapTable; +} + +- (id)imageDataLoaderForView:(UIView *)view { + id loader = nil; + if (view) { + loader = objc_getAssociatedObject(view, @selector(imageDataLoaderForView:)); + if (!loader) { + loader = [self getNewImageDataLoaderInstance]; + objc_setAssociatedObject(view, @selector(imageDataLoaderForView:), loader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + } + return loader; +} + @end diff --git a/ios/sdk/module/image loader/HippyImageLoaderModule.m b/ios/sdk/module/image loader/HippyImageLoaderModule.m index 82bcd7835..a11a9317d 100644 --- a/ios/sdk/module/image loader/HippyImageLoaderModule.m +++ b/ios/sdk/module/image loader/HippyImageLoaderModule.m @@ -24,7 +24,6 @@ #import "HippyBridge.h" #import "HippyImageLoaderModule.h" #import "HippyImageCacheManager.h" -#import "HippyImageProviderProtocol.h" @implementation HippyImageLoaderModule @@ -34,52 +33,53 @@ HIPPY_EXPORT_MODULE(ImageLoaderModule) // clang-format off HIPPY_EXPORT_METHOD(getSize:(NSString *)urlString resolver:(HippyPromiseResolveBlock)resolve rejecter:(HippyPromiseRejectBlock)reject) { - UIImage *image = [[HippyImageCacheManager sharedInstance] loadImageFromCacheForURLString:urlString radius:0 isBlurredImage:nil]; - if (image) { - NSDictionary *dic = @{@"width": @(image.size.width), @"height": @(image.size.height)}; - resolve(dic); - return; - } - NSData *uriData = [urlString dataUsingEncoding:NSUTF8StringEncoding]; - if (nil == uriData) { - NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:1 userInfo:@{@"reason": @"url parse error"}]; - reject(@"1", @"url parse error", error); - return; - } - CFURLRef urlRef = CFURLCreateWithBytes(NULL, [uriData bytes], [uriData length], kCFStringEncodingUTF8, NULL); - NSURL *source_url = CFBridgingRelease(urlRef); - - typedef void (^HandleCompletedBlock)(BOOL, NSData *, NSURL *, NSError *); - HandleCompletedBlock completedBlock = ^void(BOOL cached, NSData *data, NSURL *url, NSError *error) { - if (error) { - NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:1 userInfo:@{@"reason": @"url parse error"}]; - reject(@"2", @"url request error", error); - } else { - if (!cached) { - [[HippyImageCacheManager sharedInstance] setImageCacheData:data forURLString:urlString]; - } - Class ipClass = imageProviderClassFromBridge(data,self.bridge); - id instance = [ipClass imageProviderInstanceForData:data]; - UIImage *image = [instance image]; - if (image) { - NSDictionary *dic = @{@"width": @(image.size.width), @"height": @(image.size.height)}; - resolve(dic); - } else { - NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:2 userInfo:@{@"reason": @"image parse error"}]; - reject(@"2", @"image request error", error); - } - } - }; - - if (_bridge.imageLoader && [_bridge.imageLoader respondsToSelector: @selector(loadImage:completed:)]) { - [_bridge.imageLoader loadImage: source_url completed:^(NSData *data, NSURL *url, NSError *error, BOOL cached) { - completedBlock(cached, data, url, error); - }]; - } else { - [[[NSURLSession sharedSession] dataTaskWithURL:source_url completionHandler:^(NSData * _Nullable data, __unused NSURLResponse * _Nullable response, NSError * _Nullable error) { - completedBlock(NO, data, source_url, error); - }] resume]; - } + //TODO complete this method +// UIImage *image = [[HippyImageCacheManager sharedInstance] loadImageFromCacheForURLString:urlString radius:0 isBlurredImage:nil]; +// if (image) { +// NSDictionary *dic = @{@"width": @(image.size.width), @"height": @(image.size.height)}; +// resolve(dic); +// return; +// } +// NSData *uriData = [urlString dataUsingEncoding:NSUTF8StringEncoding]; +// if (nil == uriData) { +// NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:1 userInfo:@{@"reason": @"url parse error"}]; +// reject(@"1", @"url parse error", error); +// return; +// } +// CFURLRef urlRef = CFURLCreateWithBytes(NULL, [uriData bytes], [uriData length], kCFStringEncodingUTF8, NULL); +// NSURL *source_url = CFBridgingRelease(urlRef); +// +// typedef void (^HandleCompletedBlock)(BOOL, NSData *, NSURL *, NSError *); +// HandleCompletedBlock completedBlock = ^void(BOOL cached, NSData *data, NSURL *url, NSError *error) { +// if (error) { +// NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:1 userInfo:@{@"reason": @"url parse error"}]; +// reject(@"2", @"url request error", error); +// } else { +// if (!cached) { +// [[HippyImageCacheManager sharedInstance] setImageCacheData:data forURLString:urlString]; +// } +// Class ipClass = imageProviderClassFromBridge(data,self.bridge); +// id instance = [ipClass imageProviderInstanceForData:data]; +// UIImage *image = [instance image]; +// if (image) { +// NSDictionary *dic = @{@"width": @(image.size.width), @"height": @(image.size.height)}; +// resolve(dic); +// } else { +// NSError *error = [NSError errorWithDomain:@"ImageLoaderModuleDomain" code:2 userInfo:@{@"reason": @"image parse error"}]; +// reject(@"2", @"image request error", error); +// } +// } +// }; +// +// if (_bridge.imageLoader && [_bridge.imageLoader respondsToSelector: @selector(loadImage:completed:)]) { +// [_bridge.imageLoader loadImage: source_url completed:^(NSData *data, NSURL *url, NSError *error, BOOL cached) { +// completedBlock(cached, data, url, error); +// }]; +// } else { +// [[[NSURLSession sharedSession] dataTaskWithURL:source_url completionHandler:^(NSData * _Nullable data, __unused NSURLResponse * _Nullable response, NSError * _Nullable error) { +// completedBlock(NO, data, source_url, error); +// }] resume]; +// } } // clang-format on diff --git a/ios/sdk/utils/HippyUtils.m b/ios/sdk/utils/HippyUtils.m index f46a761a1..ff4ad86d5 100644 --- a/ios/sdk/utils/HippyUtils.m +++ b/ios/sdk/utils/HippyUtils.m @@ -515,7 +515,7 @@ BOOL HippyIsLocalAssetURL(NSURL *__nullable imageURL) { return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"]; } -HIPPY_EXTERN NSString *__nullable HippyTempFilePath(NSString *extension, NSError **error) { +NSString *__nullable HippyTempFilePath(NSString *extension, NSError **error) { static NSError *setupError = nil; static NSString *directory; static dispatch_once_t onceToken; @@ -695,7 +695,7 @@ UIColor *HippyConvertStringToColor(NSString *colorString) { return color; } -HIPPY_EXTERN UIColor *HippyConvertNumberToColor(NSInteger colorNumber) { +UIColor *HippyConvertNumberToColor(NSInteger colorNumber) { NSInteger a = (colorNumber >> 24) & 0xFF; NSInteger r = (colorNumber >> 16) & 0xFF; NSInteger g = (colorNumber >> 8) & 0xFF; @@ -765,15 +765,18 @@ NSURL *__nullable HippyURLByReplacingQueryParam(NSURL *__nullable URL, NSString NSURL *__nullable HippyURLWithString(NSString *URLString, NSString *baseURLString) { if (URLString) { NSURL *baseURL = HippyURLWithString(baseURLString, NULL); - CFURLRef URLRef = CFURLCreateWithString(NULL, (CFStringRef)URLString, (CFURLRef)baseURL); - if (URLRef) { - return CFBridgingRelease(URLRef); + NSData *uriData = [URLString dataUsingEncoding:NSUTF8StringEncoding]; + if (nil == uriData) { + return nil; } + CFURLRef urlRef = CFURLCreateWithBytes(NULL, [uriData bytes], [uriData length], kCFStringEncodingUTF8, (__bridge CFURLRef)baseURL); + NSURL *source_url = CFBridgingRelease(urlRef); + return source_url; } return nil; } -HIPPY_EXTERN NSStringEncoding HippyGetStringEncodingFromURLResponse(NSURLResponse *response) { +NSStringEncoding HippyGetStringEncodingFromURLResponse(NSURLResponse *response) { NSString *textEncoding = [response textEncodingName]; if (!textEncoding) { return NSUTF8StringEncoding; diff --git a/renderer/native/ios/HippyUIManager.mm b/renderer/native/ios/HippyUIManager.mm index eee79653f..5953a81cf 100644 --- a/renderer/native/ios/HippyUIManager.mm +++ b/renderer/native/ios/HippyUIManager.mm @@ -127,7 +127,6 @@ using HPViewBinding = std::unordered_map view, void (^block)(id)) { if (view.hippyTag) { block(view); - for (id subview in view.hippySubviews) { HippyTraverseViewNodes(subview, block); } @@ -462,7 +461,6 @@ dispatch_queue_t HippyGetUIManagerQueue(void) { [(id)subview invalidate]; } [registry removeObjectForKey:subview.hippyTag]; - if (registry == (NSMutableDictionary> *)self->_viewRegistry) { [self->_bridgeTransactionListeners removeObject:subview]; } @@ -470,28 +468,6 @@ dispatch_queue_t HippyGetUIManagerQueue(void) { } } -/** - * Disassociates children from container. Doesn't remove from registries. - * @returns Array of removed items. - */ -- (NSArray> *)childrenToRemoveFromContainer:(id)container atIndices:(NSArray *)atIndices { - // If there are no indices to move or the container has no subviews don't bother - // We support parents with nil subviews so long as they're all nil so this allows for this behavior - if (atIndices.count == 0 || [container hippySubviews].count == 0) { - return nil; - } - // Construction of removed children must be done "up front", before indices are disturbed by removals. - NSMutableArray> *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count]; - HippyAssert(container != nil, @"container view (for ID %@) not found", container); - for (NSNumber *indexNumber in atIndices) { - NSUInteger index = indexNumber.unsignedIntegerValue; - if (index < [container hippySubviews].count) { - [removedChildren addObject:[container hippySubviews][index]]; - } - } - return removedChildren; -} - - (void)removeChildren:(NSArray> *)children fromContainer:(id)container { for (id removedChild in children) { [container removeHippySubview:removedChild]; @@ -884,14 +860,14 @@ dispatch_queue_t HippyGetUIManagerQueue(void) { int32_t tag = dom_node->GetId(); HippyShadowView *shadowView = _shadowViewRegistry[@(tag)]; [shadowView removeFromHippySuperview]; - [_shadowViewRegistry removeObjectForKey:@(tag)]; + [self purgeChildren:@[shadowView] fromRegistry:(NSMutableDictionary> *)_shadowViewRegistry]; __weak auto weakSelf = self; [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { UIView *view = viewRegistry[@(tag)]; [view removeFromHippySuperview]; if (weakSelf) { auto strongSelf = weakSelf; - [strongSelf->_viewRegistry removeObjectForKey:@(tag)]; + [strongSelf purgeChildren:@[view] fromRegistry:(NSMutableDictionary> *)(strongSelf->_viewRegistry)]; } }]; }