From 5e4e889b1e744a799b6987de8a88a2f28f6e2832 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 23 Apr 2024 15:31:33 +0800 Subject: [PATCH] feat(ios): add custom image loader support (#3830) * feat(ios): add custom image loader support add HippyImageCustomLoaderProtocol * refactor(ios): refactor frame structure * chore(ios): remove obsolete comment --- .../development/ios-3.0-upgrade-guidelines.md | 8 +- docs/development/native-adapter.md | 80 +++++++++++++------ .../ios/base/bridge/HippyBridge+Private.h | 66 +++++++++++++++ framework/ios/base/bridge/HippyBridge.h | 54 +++++-------- framework/ios/base/bridge/HippyBridge.mm | 35 +++++++- .../imageloader/HippyImageLoaderModule.mm | 32 +++++--- .../imageloader/HippyImageViewCustomLoader.h | 71 ++++++++++++++++ .../module/loader/HippyBase64DataHandler.h | 4 +- .../module/loader/HippyBase64DataHandler.mm | 1 + .../module/loader/HippyBridge+VFSLoader.mm | 7 +- .../ios/module/loader/HippyFileHandler.h | 4 +- .../ios/module/loader/HippyFileHandler.mm | 1 + .../module/turbo/HippyTurboModuleManager.h | 2 +- modules/vfs/ios/HippyVFSDefines.h | 21 ++++- modules/vfs/ios/VFSUriHandler.h | 1 + modules/vfs/ios/VFSUriHandler.mm | 49 ++++++++++-- modules/vfs/ios/VFSUriLoader.h | 40 ++++++---- modules/vfs/ios/VFSUriLoader.mm | 33 +++----- .../ios/renderer/HippyUIManager+Private.h | 14 +++- renderer/native/ios/renderer/HippyUIManager.h | 5 -- .../native/ios/renderer/HippyUIManager.mm | 9 ++- .../ios/renderer/NativeRenderManager.mm | 2 +- .../component/image/HippyImageViewManager.mm | 28 +++++-- .../ios/renderer/component/view/HippyView.m | 2 +- .../component/view/HippyViewManager.mm | 14 +++- 25 files changed, 438 insertions(+), 145 deletions(-) create mode 100644 framework/ios/base/bridge/HippyBridge+Private.h create mode 100644 framework/ios/module/imageloader/HippyImageViewCustomLoader.h diff --git a/docs/development/ios-3.0-upgrade-guidelines.md b/docs/development/ios-3.0-upgrade-guidelines.md index 96f1ca68858..cb14608dc8a 100644 --- a/docs/development/ios-3.0-upgrade-guidelines.md +++ b/docs/development/ios-3.0-upgrade-guidelines.md @@ -45,6 +45,12 @@ 由于3.0中关于image source的调用约定发生了变化(从 `NSArray` 类型的 `source` 调整为了 `NSString` 类型的 `src`),因此,如自定义了Image组件,请注意在对应的ViewManager中补充实现 `src` 属性,否则图片可能无法正常显示。 -4. 删除Image组件的内置图片缓存: +4. 删除了Image组件的内置图片缓存: 3.0中删除了2.0内置的背景图片缓存管理类,即`HippyBackgroundImageCacheManager`,图片缓存逻辑交由业务方自行定制。如果您有缓存图片的需求,请通过自定义ImageLoader来实现。 + +5. 自定义ImageLoader的协议和实现变更: + + Hippy 2.0提供了`HippyImageViewCustomLoader`协议,用于业务按需定制图片资源加载器。通常,App一般使用第三方图片库实现该协议,如SDWebImage等,从而实现更灵活的图片加载和支持更多图片类型的解码。然而,2.0中的这一协议约定存在些许问题,无法达到最佳的性能表现,而且已经与3.0的VFS模块设计不再兼容,因此在3.0中我们更新了该协议的约定。 + + 注意,为便于及时发现该变更,在3.0中该协议名从`HippyImageViewCustomLoader`调整为了`HippyImageCustomLoaderProtocol`,协议方法也有一些变化,因此如果您使用了该协议,升级时将遇到少许编译问题,但其基本功能依旧保持不变。 diff --git a/docs/development/native-adapter.md b/docs/development/native-adapter.md index 8be096d8c82..5e3a83d294f 100644 --- a/docs/development/native-adapter.md +++ b/docs/development/native-adapter.md @@ -65,52 +65,86 @@ Hippy SDK 提供默认空实现 `DefaultEngineMonitorAdapter`。当你需要查 --- -## HippyImageViewCustomLoader +## HippyImageCustomLoaderProtocol -在Hippy SDK中, 前端 `` 组件默认对应的 HippyImageView 会根据 source 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageViewCustomLoader` 协议。 +在Hippy SDK中, 前端 `` 组件默认对应的 HippyImageView 会根据 src 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageCustomLoaderProtocol` 协议。 -用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。 +用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。注意该支持返回待解码的NSData类型图片数据,也支持直接返回解码后的UIImage图片,请根据需要选择合适方案。 ```objectivec -@protocol HippyImageViewCustomLoader +/// A Resource Loader for custom image loading +@protocol HippyImageCustomLoaderProtocol + @required -/** -* imageView: -*/ -- (void)imageView:(HippyImageView *)imageView - loadAtUrl:(NSURL *)url - placeholderImage:(UIImage *)placeholderImage - context:(void *)context - progress:(void (^)(long long, long long))progressBlock - completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock; - -- (void)cancelImageDownload:(HippyImageView *)imageView withUrl:(NSURL *)url; + +/// Load Image with given URL +/// Note that If you want to skip the decoding process lately, +/// such as using a third-party SDWebImage to decode, +/// Just set the ControlOptions parameters in the CompletionBlock. +/// +/// - Parameters: +/// - imageUrl: image url +/// - extraInfo: extraInfo +/// - progressBlock: progress block +/// - completedBlock: completion block +- (void)loadImageAtUrl:(NSURL *)imageUrl + extraInfo:(nullable NSDictionary *)extraInfo + progress:(nullable HippyImageLoaderProgressBlock)progressBlock + completed:(nullable HippyImageLoaderCompletionBlock)completedBlock; + @end ``` ## 协议实现 ```objectivec -@interface CustomImageLoader : NSObject +@interface CustomImageLoader : NSObject @end @implementation CustomImageLoader -HIPPY_EXPORT_MODULE() -- (void)imageView:(HippyImageView *)imageView loadAtUrl:(NSURL *)url placeholderImage:(UIImage *)placeholderImage context:(void *)context progress:(void (^)(long long, long long))progressBlock completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock { - NSError *error = NULL; +HIPPY_EXPORT_MODULE() // 全局注册该模块至Hippy + +- (void)loadImageAtUrl:(NSURL *)url + extraInfo:(NSDictionary *)extraInfo + progress:(HippyImageLoaderProgressBlock)progressBlock + completed:(HippyImageLoaderCompletionBlock)completedBlock { + + // 1、如果获取的是NSData数据: // 业务方自行获取图片数据,返回数据或者错误 + NSError *error = NULL; NSData *imageData = getImageData(url, &error); - // 将结果通过block通知 - completedBlock(imageData, url, error); + // 将结果通过block回调 + completedBlock(imageData, url, error, nil, kNilOptions); + + // 2、如果可以直接获取UIImage数据,可跳过Hippy内置解码过程,避免重复解码: + UIImage *image = getImage(xxx); + // 传入控制参数,跳过内部解码 + HippyImageLoaderControlOptions options = HippyImageLoaderControl_SkipDecodeOrDownsample; + // 将结果通过block回调 + completedBlock(nil, url, error, image, options); } @end ``` -业务方需要务必添加 `HIPPY_EXPORT_MODULE()` 代码以便在 Hippy 框架中注册此 ImageLoader 模块,系统将自动寻找实现了`HippyImageViewCustomLoader` 协议的模块当做 ImageLoader。 +## 协议注册 + +与Hippy框架注册其他模块的方法一样,ImageLoader同样既可以选择通过Hippy框架提供的 `HIPPY_EXPORT_MODULE()` 宏注册到App全局(注意,全局注册的含义是App内的所有HippyBridge实例均会获取和使用该模块),又可通过 `HippyBridge` 初始化参数列表中的 `moduleProvider` 参数来注册到特定bridge。 + +除此之外,`HippyBridge` 还提供了一个注册方法,便于业务注册ImageLoader实例: + +```objectivec +/// Set a custom Image Loader for current `hippyBridge` +/// The globally registered ImageLoader is ignored when set by this method. +/// +/// - Parameter imageLoader: id +- (void)setCustomImageLoader:(id)imageLoader; +``` + +在上述实现代码中,我们使用了 `HIPPY_EXPORT_MODULE()` 宏来实现将此 ImageLoader 模块自动注册至 Hippy 框架中,框架内部将自动寻找实现了`HippyImageCustomLoaderProtocol` 协议的模块作为 ImageLoader。 -PS: 若有多个模块实现 `HippyImageViewCustomLoader` 协议,系统只会使用其中一个作为默认 ImageLoader +!> 注意,同时只可有一个ImageLoader生效。若有多个模块实现了 `HippyImageCustomLoaderProtocol` 协议,框架使用最后一个作为生效的 ImageLoader。Hippy框架优先使用通过 `setCustomImageLoader:` 方法注册的ImageLoader。 diff --git a/framework/ios/base/bridge/HippyBridge+Private.h b/framework/ios/base/bridge/HippyBridge+Private.h new file mode 100644 index 00000000000..faca1cdc9f4 --- /dev/null +++ b/framework/ios/base/bridge/HippyBridge+Private.h @@ -0,0 +1,66 @@ +/*! + * 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. + */ + +#ifndef HippyBridge_Private_h +#define HippyBridge_Private_h + +#import "HippyBridge.h" +#include + +class VFSUriLoader; +class NativeRenderManager; + +namespace hippy { +inline namespace dom { +class DomManager; +class RootNode; +class RenderManager; +}; +}; + + +@protocol HippyBridgeInternal + +/// The C++ version of RenderManager instance, bridge holds +@property (nonatomic, assign) std::shared_ptr renderManager; + +/// URI Loader +@property (nonatomic, assign) std::weak_ptr vfsUriLoader; + +@end + + +@interface HippyBridge (Private) + +/** + * Set basic configuration for native render + * @param domManager DomManager + * @param rootNode RootNode + */ +- (void)setupDomManager:(std::shared_ptr)domManager + rootNode:(std::weak_ptr)rootNode; + +@end + + + +#endif /* HippyBridge_Private_h */ diff --git a/framework/ios/base/bridge/HippyBridge.h b/framework/ios/base/bridge/HippyBridge.h index 642c1d98d5b..9b37f2a9ffd 100644 --- a/framework/ios/base/bridge/HippyBridge.h +++ b/framework/ios/base/bridge/HippyBridge.h @@ -26,29 +26,14 @@ #import "HippyMethodInterceptorProtocol.h" #import "HippyModulesSetup.h" #import "HippyImageProviderProtocol.h" +#import "HippyImageViewCustomLoader.h" #import "HippyInvalidating.h" #import "HippyDefines.h" -#ifdef __cplusplus -#include -#endif - @class HippyJSExecutor; @class HippyModuleData; @class HippyRootView; -#ifdef __cplusplus -class VFSUriLoader; -class NativeRenderManager; - -namespace hippy { -inline namespace dom { -class DomManager; -class RootNode; -class RenderManager; -}; -}; -#endif NS_ASSUME_NONNULL_BEGIN @@ -167,9 +152,23 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); - (void)loadBundleURL:(NSURL *)bundleURL completion:(void (^_Nullable)(NSURL * _Nullable, NSError * _Nullable))completion; -#ifdef __cplusplus -@property(nonatomic, assign)std::weak_ptr VFSUriLoader; -#endif + +#pragma mark - Image Related + +/// Get the custom Image Loader +/// +/// Note that A custom ImageLoader can be registered in two ways: +/// One is through the registration method provided below, +/// The other is to register globally with the HIPPY_EXPORT_MODULE macro. +/// +/// Only one image loader can take effect at a time. +@property (nonatomic, strong, nullable, readonly) id imageLoader; + +/// Set a custom Image Loader for current `hippyBridge` +/// The globally registered ImageLoader is ignored when set by this method. +/// +/// - Parameter imageLoader: id +- (void)setCustomImageLoader:(id)imageLoader; /** * Image provider method @@ -178,15 +177,8 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); - (void)addImageProviderClass:(Class)cls; - (NSArray> *)imageProviderClasses; -#ifdef __cplusplus -/** - * Set basic configuration for native render - * @param domManager DomManager - * @param rootNode RootNode - */ -- (void)setupDomManager:(std::shared_ptr)domManager - rootNode:(std::weak_ptr)rootNode; -#endif + +#pragma mark - /** * Load instance for root view and show views @@ -207,12 +199,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass); @property (nonatomic, readonly) HippyJSExecutor *javaScriptExecutor; -#ifdef __cplusplus -/// The C++ version of RenderManager instance, bridge holds -@property (nonatomic, assign) std::shared_ptr renderManager; -#endif - - /** * JS invocation methods */ diff --git a/framework/ios/base/bridge/HippyBridge.mm b/framework/ios/base/bridge/HippyBridge.mm index d1767140d6b..c6b6b166692 100644 --- a/framework/ios/base/bridge/HippyBridge.mm +++ b/framework/ios/base/bridge/HippyBridge.mm @@ -21,6 +21,7 @@ */ #import "HippyBridge.h" +#import "HippyBridge+Private.h" #import "HippyBundleLoadOperation.h" #import "HippyBundleExecutionOperation.h" #import "HippyBundleOperationQueue.h" @@ -173,6 +174,9 @@ @interface HippyBridge() { @implementation HippyBridge +@synthesize renderManager = _renderManager; +@synthesize imageLoader = _imageLoader; + dispatch_queue_t HippyJSThread; dispatch_queue_t HippyBridgeQueue() { @@ -238,7 +242,7 @@ - (instancetype)initWithDelegate:(id)delegate [self setUp]; [self addImageProviderClass:[HippyDefaultImageProvider class]]; - [self setVFSUriLoader:[self createURILoaderIfNeeded]]; + [self setVfsUriLoader:[self createURILoaderIfNeeded]]; [self setUpNativeRenderManager]; [HippyBridge setCurrentBridge:self]; @@ -404,6 +408,31 @@ - (BOOL)moduleIsInitialized:(Class)moduleClass { } +#pragma mark - Image Config Related + +- (id)imageLoader { + @synchronized (self) { + if (!_imageLoader) { + // Only the last imageloader takes effect, + // compatible with Hippy 2.x + _imageLoader = [[self modulesConformingToProtocol:@protocol(HippyImageCustomLoaderProtocol)] lastObject]; + } + } + return _imageLoader; +} + +- (void)setCustomImageLoader:(id)imageLoader { + @synchronized (self) { + if (imageLoader != _imageLoader) { + if (_imageLoader) { + HippyLogWarn(@"ImageLoader change from %@ to %@", _imageLoader, imageLoader); + } + _imageLoader = imageLoader; + } + } +} + + #pragma mark - Debug Reload - (void)reload { @@ -607,7 +636,7 @@ - (void)rootViewSizeChangedEvent:(NSNumber *)tag params:(NSDictionary *)params { [self sendEvent:@"onSizeChanged" params:dic]; } -- (void)setVFSUriLoader:(std::weak_ptr)uriLoader { +- (void)setVfsUriLoader:(std::weak_ptr)uriLoader { [_javaScriptExecutor setUriLoader:uriLoader]; #ifdef ENABLE_INSPECTOR auto devtools_data_source = _javaScriptExecutor.pScope->GetDevtoolsDataSource(); @@ -621,7 +650,7 @@ - (void)setVFSUriLoader:(std::weak_ptr)uriLoader { #endif } -- (std::weak_ptr)VFSUriLoader { +- (std::weak_ptr)vfsUriLoader { return _uriLoader; } diff --git a/framework/ios/module/imageloader/HippyImageLoaderModule.mm b/framework/ios/module/imageloader/HippyImageLoaderModule.mm index 3f98dd65f05..a908df9ad1f 100644 --- a/framework/ios/module/imageloader/HippyImageLoaderModule.mm +++ b/framework/ios/module/imageloader/HippyImageLoaderModule.mm @@ -21,12 +21,14 @@ */ #import - +#import "HippyBridge+Private.h" #import "HippyBridge+VFSLoader.h" #import "HippyImageLoaderModule.h" #import "HippyUtils.h" #import "HippyDefines.h" #import "HippyLog.h" +#import "VFSUriLoader.h" + static NSString *const kImageLoaderModuleErrorDomain = @"kImageLoaderModuleErrorDomain"; static NSUInteger const ImageLoaderErrorParseError = 2; @@ -93,15 +95,25 @@ @implementation HippyImageLoaderModule } HIPPY_EXPORT_METHOD(prefetch:(NSString *)urlString) { - [self.bridge loadContentsAsynchronouslyFromUrl:urlString - method:@"Get" - params:nil - body:nil - queue:nil - progress:nil - completionHandler:^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { - HippyLogInfo(@"prefetch %@ complete, err? %@", urlString, error.description); - }]; + if (!urlString || !self.bridge) { + return; + } + id customLoader = self.bridge.imageLoader; + NSDictionary *extraReqInfo; + if (customLoader) { + extraReqInfo = @{ kHippyVFSRequestResTypeKey:@(HippyVFSRscTypeImage), + kHippyVFSRequestCustomImageLoaderKey: customLoader }; + } + + auto loader = [self.bridge vfsUriLoader].lock(); + if (loader) { + NSURL *url = HippyURLWithString(urlString, nil); + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + loader->RequestUntrustedContent(request, extraReqInfo, nil, nil, + ^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { + HippyLogInfo(@"prefetch %@ complete, err? %@", urlString, error.description); + }); + } } @end diff --git a/framework/ios/module/imageloader/HippyImageViewCustomLoader.h b/framework/ios/module/imageloader/HippyImageViewCustomLoader.h new file mode 100644 index 00000000000..71068ed306d --- /dev/null +++ b/framework/ios/module/imageloader/HippyImageViewCustomLoader.h @@ -0,0 +1,71 @@ +/*! + * 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 "HippyBridgeModule.h" +@class HippyImageView; + +NS_ASSUME_NONNULL_BEGIN + +// The ImageView object in `extraInfo` parameter of `loadImageAtUrl:...` +#define HIPPY_CUSTOMLOADER_IMAGEVIEW_IN_EXTRA_KEY "kHippyImageViewInExtra" + + +typedef NS_OPTIONS(NSUInteger, HippyImageLoaderControlOptions) { + HippyImageLoaderControl_Nil = 0, // kNilOptions + /** + * This flag controls the HippyImageView from doing image data decoding or downsampling, + * Only takes effect when image is not null in the completion block. + */ + HippyImageLoaderControl_SkipDecodeOrDownsample = 1 << 0, +}; + +typedef void(^HippyImageLoaderProgressBlock)(NSUInteger currentLength, NSUInteger totalLength); +typedef void(^HippyImageLoaderCompletionBlock)(NSData *_Nullable data, + NSURL * _Nonnull url, + NSError *_Nullable error, + UIImage *_Nullable image, + HippyImageLoaderControlOptions options); + + +/// A Resource Loader for custom image loading +@protocol HippyImageCustomLoaderProtocol + +@required + +/// Load Image with given URL +/// Note that If you want to skip the decoding process lately, +/// such as using a third-party SDWebImage to decode, +/// Just set the ControlOptions parameters in the CompletionBlock. +/// +/// - Parameters: +/// - imageUrl: image url +/// - extraInfo: extraInfo +/// - progressBlock: progress block +/// - completedBlock: completion block +- (void)loadImageAtUrl:(NSURL *)imageUrl + extraInfo:(nullable NSDictionary *)extraInfo + progress:(nullable HippyImageLoaderProgressBlock)progressBlock + completed:(nullable HippyImageLoaderCompletionBlock)completedBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/framework/ios/module/loader/HippyBase64DataHandler.h b/framework/ios/module/loader/HippyBase64DataHandler.h index 64bea39a1fb..1f52cd71600 100644 --- a/framework/ios/module/loader/HippyBase64DataHandler.h +++ b/framework/ios/module/loader/HippyBase64DataHandler.h @@ -29,9 +29,11 @@ class HippyBase64DataHandler : public VFSUriHandler { public: void RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *queue, VFSHandlerProgressBlock progress, - VFSHandlerCompletionBlock completion, VFSGetNextHandlerBlock next) override; + VFSHandlerCompletionBlock completion, + VFSGetNextHandlerBlock next) override; }; diff --git a/framework/ios/module/loader/HippyBase64DataHandler.mm b/framework/ios/module/loader/HippyBase64DataHandler.mm index c1cc0634573..7f539269468 100644 --- a/framework/ios/module/loader/HippyBase64DataHandler.mm +++ b/framework/ios/module/loader/HippyBase64DataHandler.mm @@ -23,6 +23,7 @@ #import "HippyBase64DataHandler.h" void HippyBase64DataHandler::RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *queue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion, diff --git a/framework/ios/module/loader/HippyBridge+VFSLoader.mm b/framework/ios/module/loader/HippyBridge+VFSLoader.mm index 2a4bec80109..4c2de791b7b 100644 --- a/framework/ios/module/loader/HippyBridge+VFSLoader.mm +++ b/framework/ios/module/loader/HippyBridge+VFSLoader.mm @@ -21,11 +21,12 @@ */ #import "HippyBridge+VFSLoader.h" +#import "HippyBridge+Private.h" #import "HippyUtils.h" - #include "VFSUriLoader.h" #include "VFSUriHandler.h" + @implementation HippyBridge (VFSLoader) - (void)loadContentsAsynchronouslyFromUrl:(NSString *)urlString @@ -38,7 +39,7 @@ - (void)loadContentsAsynchronouslyFromUrl:(NSString *)urlString if (!urlString || !completionHandler) { return; } - std::shared_ptr loader = [self VFSUriLoader].lock(); + std::shared_ptr loader = [self vfsUriLoader].lock(); if (loader) { NSURL *url = HippyURLWithString(urlString, nil); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; @@ -53,7 +54,7 @@ - (void)loadContentsAsynchronouslyFromUrl:(NSString *)urlString if (body) { [request setHTTPBody:body]; } - loader->RequestUntrustedContent(request, queue, progress, completionHandler); + loader->RequestUntrustedContent(request, nil, queue, progress, completionHandler); } } diff --git a/framework/ios/module/loader/HippyFileHandler.h b/framework/ios/module/loader/HippyFileHandler.h index 9e9f6bd6b3a..924a9759aff 100644 --- a/framework/ios/module/loader/HippyFileHandler.h +++ b/framework/ios/module/loader/HippyFileHandler.h @@ -41,9 +41,11 @@ class HippyFileHandler : public VFSUriHandler { std::function()> next) override; virtual void RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *queue, VFSHandlerProgressBlock progress, - VFSHandlerCompletionBlock completion, VFSGetNextHandlerBlock next) override; + VFSHandlerCompletionBlock completion, + VFSGetNextHandlerBlock next) override; private: __weak HippyBridge *bridge_; }; diff --git a/framework/ios/module/loader/HippyFileHandler.mm b/framework/ios/module/loader/HippyFileHandler.mm index 91fd0827bca..2532c5f53cf 100644 --- a/framework/ios/module/loader/HippyFileHandler.mm +++ b/framework/ios/module/loader/HippyFileHandler.mm @@ -43,6 +43,7 @@ } void HippyFileHandler::RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *queue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion, diff --git a/framework/ios/module/turbo/HippyTurboModuleManager.h b/framework/ios/module/turbo/HippyTurboModuleManager.h index 6a56b541d5c..a971e9436e5 100644 --- a/framework/ios/module/turbo/HippyTurboModuleManager.h +++ b/framework/ios/module/turbo/HippyTurboModuleManager.h @@ -21,9 +21,9 @@ */ #import - #import "HippyBridge.h" #import "HippyOCTurboModule.h" +#import namespace hippy { inline namespace driver { diff --git a/modules/vfs/ios/HippyVFSDefines.h b/modules/vfs/ios/HippyVFSDefines.h index 7acd11e6517..ecadb88f2d2 100644 --- a/modules/vfs/ios/HippyVFSDefines.h +++ b/modules/vfs/ios/HippyVFSDefines.h @@ -34,8 +34,25 @@ constexpr char kRequestFromOC[] = "kRequestFromOC"; constexpr char kHeaderBody[] = "kHeaderBody"; constexpr char kHeaderMethod[] = "kHeaderMethod"; -// The image returned in userInfo of VFSHandlerCompletionBlock -FOUNDATION_EXPORT NSString *_Nonnull const HippyVFSHandlerUserInfoImageKey; +enum HippyVFSRscType { + HippyVFSRscTypeOther = 0, + HippyVFSRscTypeImage, +}; + + +// Resource Type Key for VFS Request in `extraInfo` parameter, +// Value is defined in HippyVFSRscType +FOUNDATION_EXPORT NSString *_Nonnull const kHippyVFSRequestResTypeKey; + +// Custom Image Loader (id) instance for Image request +FOUNDATION_EXPORT NSString *_Nonnull const kHippyVFSRequestCustomImageLoaderKey; + +// Store `ExtraInfo` dictionary for Custom Image Loader +FOUNDATION_EXPORT NSString *_Nonnull const kHippyVFSRequestExtraInfoForCustomImageLoaderKey; + +// The image returned in `userInfo` parameter of VFSHandlerCompletionBlock +FOUNDATION_EXPORT NSString *_Nonnull const HippyVFSResponseDecodedImageKey; + typedef void(^VFSHandlerProgressBlock)(NSUInteger current, NSUInteger total); typedef void(^VFSHandlerCompletionBlock)(NSData *_Nullable data, diff --git a/modules/vfs/ios/VFSUriHandler.h b/modules/vfs/ios/VFSUriHandler.h index d4b7eb3b12a..cf640f18225 100644 --- a/modules/vfs/ios/VFSUriHandler.h +++ b/modules/vfs/ios/VFSUriHandler.h @@ -39,6 +39,7 @@ class VFSUriHandler : public hippy::vfs::UriHandler { std::function()> next) override; virtual void RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *queue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion, diff --git a/modules/vfs/ios/VFSUriHandler.mm b/modules/vfs/ios/VFSUriHandler.mm index 5fce5f6f2c3..768468ec890 100644 --- a/modules/vfs/ios/VFSUriHandler.mm +++ b/modules/vfs/ios/VFSUriHandler.mm @@ -21,21 +21,24 @@ */ #import - #import "HippyFootstoneUtils.h" #import "NSURLResponse+ToUnorderedMap.h" #import "NSURLSessionDataProgress.h" #import "TypeConverter.h" #import "VFSUriHandler.h" #import "VFSUriLoader.h" - -#include - +#import "HippyImageViewCustomLoader.h" #include "footstone/string_view_utils.h" #include "vfs/uri_loader.h" +#include + static char *progressKey = nullptr; -NSString *const HippyVFSHandlerUserInfoImageKey = @"HippyVFSHandlerUserInfoImageKey"; +NSString *const kHippyVFSRequestResTypeKey = @"kHippyVFSRequestResTypeKey"; +NSString *const kHippyVFSRequestCustomImageLoaderKey = @"kHippyVFSRequestCustomImageLoaderKey"; +NSString *const kHippyVFSRequestExtraInfoForCustomImageLoaderKey = @"kHippyVFSRequestExtraInfoForCustomImageLoaderKey"; +NSString *const HippyVFSResponseDecodedImageKey = @"HippyVFSResponseDecodedImageKey"; + static bool CheckRequestFromCPP(const std::unordered_map &headers) { auto find = headers.find(kRequestOrigin); @@ -155,7 +158,7 @@ static bool CheckRequestFromCPP(const std::unordered_mapRequestUntrustedContent(req, nil, ^(NSUInteger current, NSUInteger total) { + loader->RequestUntrustedContent(req, nil, nil, ^(NSUInteger current, NSUInteger total) { request->GetProgressCallback()(current, total); }, ^(NSData *data, NSDictionary *userInfo, NSURLResponse *resp, NSError *error) { RetCode code = RetCodeFromNSError(error); @@ -176,6 +179,7 @@ static bool CheckRequestFromCPP(const std::unordered_map customLoader = extraInfo[kHippyVFSRequestCustomImageLoaderKey]; + NSDictionary *extraForCustomLoader = extraInfo[kHippyVFSRequestExtraInfoForCustomImageLoaderKey]; + + [customLoader loadImageAtUrl:requestURL + extraInfo:extraForCustomLoader + progress:progress + completed:^(NSData * _Nullable data, + NSURL * _Nonnull url, + NSError * _Nullable error, + UIImage * _Nullable image, + HippyImageLoaderControlOptions options) { + NSDictionary *dict = nil; + if (image && (options & HippyImageLoaderControl_SkipDecodeOrDownsample)) { + dict = @{ HippyVFSResponseDecodedImageKey: image }; + } + NSURLResponse *rsp = [[NSURLResponse alloc] initWithURL:url + MIMEType:nil + expectedContentLength:data.length + textEncodingName:nil]; + completion(data, dict, rsp, error); + }]; + return; + } + NSDictionary *httpHeaders = [request allHTTPHeaderFields]; if ([httpHeaders[@(kRequestOrigin)] isEqualToString:@(kRequestFromOC)]) { NSDictionary *userInfo = @{NSURLErrorFailingURLErrorKey: requestURL, @@ -201,7 +236,7 @@ static bool CheckRequestFromCPP(const std::unordered_mapRequestUntrustedContent(request, queue, progress, completion, next); + nextHandler->RequestUntrustedContent(request, extraInfo, queue, progress, completion, next); } else { //try to forward to cpp uri handler diff --git a/modules/vfs/ios/VFSUriLoader.h b/modules/vfs/ios/VFSUriLoader.h index 4bc86e3ef82..72545e3095e 100644 --- a/modules/vfs/ios/VFSUriLoader.h +++ b/modules/vfs/ios/VFSUriLoader.h @@ -21,7 +21,6 @@ */ #import - #include "HippyVFSDefines.h" #include "vfs/uri_loader.h" @@ -39,20 +38,20 @@ class VFSUriLoader : public hippy::vfs::UriLoader { VFSUriLoader() = default; ~VFSUriLoader() = default; - virtual void RequestUntrustedContent( - const string_view& uri, - const std::unordered_map& meta, - std::function, bytes)> cb) override; + virtual void RequestUntrustedContent(const string_view& uri, + const std::unordered_map& meta, + std::function, bytes)> cb) override; - virtual void RequestUntrustedContent( - const string_view& uri, - const std::unordered_map& req_meta, - RetCode& code, - std::unordered_map& rsp_meta, - bytes& content) override; + virtual void RequestUntrustedContent(const string_view& uri, + const std::unordered_map& req_meta, + RetCode& code, + std::unordered_map& rsp_meta, + bytes& content) override; - virtual void RequestUntrustedContent(const std::shared_ptr& request, std::shared_ptr response) override; - virtual void RequestUntrustedContent(const std::shared_ptr& request, const std::function)>& cb) override; + virtual void RequestUntrustedContent(const std::shared_ptr& request, + std::shared_ptr response) override; + virtual void RequestUntrustedContent(const std::shared_ptr& request, + const std::function)>& cb) override; //Foundation API convenient methods virtual void RegisterConvenientUriHandler(NSString *scheme, @@ -60,9 +59,18 @@ class VFSUriLoader : public hippy::vfs::UriLoader { virtual void AddConvenientDefaultHandler(const std::shared_ptr& handler); virtual const std::list> &GetConvenientDefaultHandlers(); - virtual void RequestUntrustedContent(NSString *urlString, NSOperationQueue *operationQueue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion); - virtual void RequestUntrustedContent(NSString *urlString, NSString *method, NSOperationQueue *operationQueue, NSDictionary *httpHeader, NSData *body, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion); - virtual void RequestUntrustedContent(NSURLRequest *request, NSOperationQueue *operationQueue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion); + + virtual void RequestUntrustedContent(NSString *urlString, + NSDictionary *extraInfo, + NSOperationQueue *operationQueue, + VFSHandlerProgressBlock progress, + VFSHandlerCompletionBlock completion); + virtual void RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, + NSOperationQueue *operationQueue, + VFSHandlerProgressBlock progress, + VFSHandlerCompletionBlock completion); + private: std::shared_ptr GetNextConvinentHandler(std::list>::iterator &cur_con_handler_it, const std::list>::iterator &end_con_handler_it); diff --git a/modules/vfs/ios/VFSUriLoader.mm b/modules/vfs/ios/VFSUriLoader.mm index 9857317db85..0825443c4ba 100644 --- a/modules/vfs/ios/VFSUriLoader.mm +++ b/modules/vfs/ios/VFSUriLoader.mm @@ -86,35 +86,20 @@ hippy::vfs::UriLoader::RequestUntrustedContent(request, cb); } -void VFSUriLoader::RequestUntrustedContent(NSString *urlString, NSOperationQueue *operationQueue, +void VFSUriLoader::RequestUntrustedContent(NSString *urlString, + NSDictionary *extraInfo, + NSOperationQueue *operationQueue, VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion) { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]; - RequestUntrustedContent(request, operationQueue, progress, completion); + RequestUntrustedContent(request, extraInfo, operationQueue, progress, completion); } -void VFSUriLoader::RequestUntrustedContent(NSString *urlString, NSString *method, +void VFSUriLoader::RequestUntrustedContent(NSURLRequest *request, + NSDictionary *extraInfo, NSOperationQueue *operationQueue, - NSDictionary *httpHeader, NSData *body, - VFSHandlerProgressBlock progress, + VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion) { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]]; - if (method) { - [request setHTTPMethod:method]; - } - if (httpHeader) { - for (NSString *key in httpHeader) { - [request setValue:httpHeader[key] forHTTPHeaderField:key]; - } - } - if (body) { - [request setHTTPBody:body]; - } - RequestUntrustedContent(request, operationQueue, progress, completion); -} - -void VFSUriLoader::RequestUntrustedContent(NSURLRequest *request, NSOperationQueue *operationQueue, - VFSHandlerProgressBlock progress, VFSHandlerCompletionBlock completion) { if (!request || !completion) { return; } @@ -154,8 +139,10 @@ completion(data, userInfo, response, error); } }; - cur_convenient->RequestUntrustedContent(request, operationQueue, progress, callback, block); + cur_convenient->RequestUntrustedContent(request, extraInfo, operationQueue, progress, callback, block); } else { + // TODO: when forward to cpp loader + // cpp loader does not handle rscType string_view uri = NSStringToU8StringView([requestURL absoluteString]); auto meta = NSDictionaryToStringUnorderedMap([request allHTTPHeaderFields]); auto progressCallback = [progress, operationQueue](int64_t current, int64_t total){ diff --git a/renderer/native/ios/renderer/HippyUIManager+Private.h b/renderer/native/ios/renderer/HippyUIManager+Private.h index 64659f472be..83286ef3aff 100644 --- a/renderer/native/ios/renderer/HippyUIManager+Private.h +++ b/renderer/native/ios/renderer/HippyUIManager+Private.h @@ -47,7 +47,19 @@ class HippyValue; } } -@interface HippyUIManager (Private) + +@protocol HippyUIManagerInternal + +/// DomManager instance +@property (nonatomic, readonly) std::weak_ptr domManager; + +/// VFSUriLoader instance +@property (nonatomic, assign) std::weak_ptr vfsUriLoader; + +@end + + +@interface HippyUIManager (Private) /// Set hippy::RenderManager /// - Parameter renderManager: hippy::RenderManager diff --git a/renderer/native/ios/renderer/HippyUIManager.h b/renderer/native/ios/renderer/HippyUIManager.h index 4009108c1f7..5d9c3b22755 100644 --- a/renderer/native/ios/renderer/HippyUIManager.h +++ b/renderer/native/ios/renderer/HippyUIManager.h @@ -66,11 +66,6 @@ HIPPY_EXTERN NSString *const HippyUIManagerDidEndBatchNotification; /// The HippyUIManager responsible for updating the view hierarchy. @interface HippyUIManager : NSObject -#ifdef __cplusplus -@property (nonatomic, assign) std::weak_ptr VFSUriLoader; -@property (nonatomic, readonly) std::weak_ptr domManager; -#endif - @property (nonatomic, weak) HippyBridge *bridge; @property (nonatomic, readonly) HippyComponentMap *viewRegistry; @property (nonatomic, assign) BOOL uiCreationLazilyEnabled; diff --git a/renderer/native/ios/renderer/HippyUIManager.mm b/renderer/native/ios/renderer/HippyUIManager.mm index 18bc1e1930c..a4c832ccbe4 100644 --- a/renderer/native/ios/renderer/HippyUIManager.mm +++ b/renderer/native/ios/renderer/HippyUIManager.mm @@ -47,12 +47,13 @@ #import "HippyModulesSetup.h" #import "NativeRenderManager.h" #import "HippyShadowListView.h" -#import "dom/root_node.h" -#import "objc/runtime.h" -#import #import "HippyModuleData.h" #import "HippyModuleMethod.h" +#import "HippyBridge+Private.h" +#import "dom/root_node.h" +#import "objc/runtime.h" #import +#import using HippyValue = footstone::value::HippyValue; @@ -185,7 +186,6 @@ @interface HippyUIManager() { // The implementation here needs to be improved to provide a registration mechanism. NSHashTable> *_componentTransactionListeners; - std::weak_ptr _domManager; std::weak_ptr _renderManager; std::mutex _renderQueueLock; @@ -209,6 +209,7 @@ @interface HippyUIManager() { @implementation HippyUIManager @synthesize domManager = _domManager; +@synthesize vfsUriLoader = _vfsUriLoader; #pragma mark Life cycle diff --git a/renderer/native/ios/renderer/NativeRenderManager.mm b/renderer/native/ios/renderer/NativeRenderManager.mm index 440abb710db..463786279c7 100644 --- a/renderer/native/ios/renderer/NativeRenderManager.mm +++ b/renderer/native/ios/renderer/NativeRenderManager.mm @@ -222,7 +222,7 @@ void NativeRenderManager::SetVFSUriLoader(std::shared_ptr loader) { @autoreleasepool { HippyAssert(renderImpl_, @"renderImpl_ is null, did you forget to call Initialize()?"); - renderImpl_.VFSUriLoader = loader; + renderImpl_.vfsUriLoader = loader; } } diff --git a/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm b/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm index cf1a2319b30..9f925f814db 100644 --- a/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm +++ b/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm @@ -27,6 +27,7 @@ #import "HippyUIManager.h" #import "TypeConverter.h" #import "VFSUriLoader.h" +#import "HippyBridge+Private.h" @implementation HippyImageViewManager @@ -50,6 +51,7 @@ @implementation HippyImageViewManager HIPPY_CUSTOM_VIEW_PROPERTY(src, NSString, HippyImageView) { NSString *path = [HippyConvert NSString:json]; + view.source = @[@{@"uri": path}]; // for compatible with Hippy2 [self loadImageSource:path forView:view]; } @@ -69,12 +71,19 @@ @implementation HippyImageViewManager HIPPY_CUSTOM_VIEW_PROPERTY(defaultSource, NSString, HippyImageView) { NSString *source = [HippyConvert NSString:json]; - auto loader = [self.bridge.uiManager VFSUriLoader].lock(); + auto loader = [self.bridge vfsUriLoader].lock(); if (!loader) { return; } + id customLoader = self.bridge.imageLoader; + NSDictionary *extraReqInfo; + if (customLoader) { + extraReqInfo = @{ kHippyVFSRequestResTypeKey:@(HippyVFSRscTypeImage), + kHippyVFSRequestCustomImageLoaderKey: customLoader}; + } + __weak HippyImageView *weakView = view; - loader->RequestUntrustedContent(source, imageLoadOperationQueue(), + loader->RequestUntrustedContent(source, extraReqInfo, imageLoadOperationQueue(), nil, ^(NSData * _Nullable data, NSDictionary * _Nullable userInfo, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -105,12 +114,21 @@ - (void)loadImageSource:(NSString *)path forView:(HippyImageView *)view { } NSString *standardizeAssetUrlString = path; __weak HippyImageView *weakView = view; - auto loader = [self.bridge.uiManager VFSUriLoader].lock(); + auto loader = [self.bridge vfsUriLoader].lock(); if (!loader) { return; } + id customLoader = self.bridge.imageLoader; + NSDictionary *extraReqInfo; + if (customLoader) { + NSDictionary *infoForLoader = @{ @(HIPPY_CUSTOMLOADER_IMAGEVIEW_IN_EXTRA_KEY): view }; + extraReqInfo = @{ kHippyVFSRequestResTypeKey:@(HippyVFSRscTypeImage), + kHippyVFSRequestCustomImageLoaderKey: customLoader, + kHippyVFSRequestExtraInfoForCustomImageLoaderKey: infoForLoader }; + } + __weak __typeof(self)weakSelf = self; - loader->RequestUntrustedContent(path, imageLoadOperationQueue(), nil, + loader->RequestUntrustedContent(path, extraReqInfo, imageLoadOperationQueue(), nil, ^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { __strong __typeof(weakSelf)strongSelf = weakSelf; HippyBridge *bridge = strongSelf.bridge; @@ -127,7 +145,7 @@ - (void)loadImageSource:(NSString *)path forView:(HippyImageView *)view { [imageProvider setImageData:data]; // It is possible for User to return the image directly in userInfo, // So we need to check and skip the data decoding process if needed. - UIImage *resultImage = userInfo ? userInfo[HippyVFSHandlerUserInfoImageKey] : nil; + UIImage *resultImage = userInfo ? userInfo[HippyVFSResponseDecodedImageKey] : nil; if (resultImage) { [imageProvider setDecodedImage:resultImage]; } diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index c3e2a05a8b4..d577002c0ba 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -102,7 +102,7 @@ - (NSString *)accessibilityLabel { - (NSString *)description { NSString *superDescription = super.description; NSRange semicolonRange = [superDescription rangeOfString:@";"]; - NSString *replacement = [NSString stringWithFormat:@"; componentTag: %@;", self.hippyTag]; + NSString *replacement = [NSString stringWithFormat:@"; hippyTag: %@;", self.hippyTag]; return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement]; } diff --git a/renderer/native/ios/renderer/component/view/HippyViewManager.mm b/renderer/native/ios/renderer/component/view/HippyViewManager.mm index 3fbbb2d0ed9..3b28c925fee 100644 --- a/renderer/native/ios/renderer/component/view/HippyViewManager.mm +++ b/renderer/native/ios/renderer/component/view/HippyViewManager.mm @@ -34,6 +34,7 @@ #import "UIView+DirectionalLayout.h" #import "UIView+Hippy.h" #import "HippyBridgeModule.h" +#import "HippyBridge+Private.h" #import #import "VFSUriLoader.h" #import "dom/layout_node.h" @@ -263,19 +264,26 @@ - (void)loadImageSource:(NSString *)path forView:(HippyView *)view { return; } NSString *standardizeAssetUrlString = path; - auto loader = [self.bridge.uiManager VFSUriLoader].lock(); + auto loader = [self.bridge vfsUriLoader].lock(); if (!loader) { return; } + id customLoader = self.bridge.imageLoader; + NSDictionary *extraReqInfo; + if (customLoader) { + extraReqInfo = @{ kHippyVFSRequestResTypeKey:@(HippyVFSRscTypeImage), + kHippyVFSRequestCustomImageLoaderKey: customLoader }; + } + __weak __typeof(self)weakSelf = self; __weak HippyView *weakView = view; - loader->RequestUntrustedContent(path, imageLoadOperationQueue(), nil, + loader->RequestUntrustedContent(path, extraReqInfo, imageLoadOperationQueue(), nil, ^(NSData *data, NSDictionary *userInfo, NSURLResponse *response, NSError *error) { HippyLogTrace(@"%@ load bgImg finish:%@, hash:%lu record:%lu error?%@", weakView.hippyTag, path, path.hash, weakView.backgroundImageUrlHash, error.description); // It is possible for User to return the image directly in userInfo, // So we need to check and skip the data decoding process if needed. - UIImage *resultImage = userInfo ? userInfo[HippyVFSHandlerUserInfoImageKey] : nil; + UIImage *resultImage = userInfo ? userInfo[HippyVFSResponseDecodedImageKey] : nil; if (resultImage) { dispatch_async(dispatch_get_main_queue(), ^{ __strong HippyView *strongView = weakView;