Skip to content

Commit

Permalink
feat(ios): add custom image loader support (#3830)
Browse files Browse the repository at this point in the history
* feat(ios): add custom image loader support

add HippyImageCustomLoaderProtocol

* refactor(ios): refactor frame structure

* chore(ios): remove obsolete comment
  • Loading branch information
wwwcg authored Apr 23, 2024
1 parent 8f72368 commit 5e4e889
Show file tree
Hide file tree
Showing 25 changed files with 438 additions and 145 deletions.
8 changes: 7 additions & 1 deletion docs/development/ios-3.0-upgrade-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,协议方法也有一些变化,因此如果您使用了该协议,升级时将遇到少许编译问题,但其基本功能依旧保持不变。
80 changes: 57 additions & 23 deletions docs/development/native-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,52 +65,86 @@ Hippy SDK 提供默认空实现 `DefaultEngineMonitorAdapter`。当你需要查

---

## HippyImageViewCustomLoader
## HippyImageCustomLoaderProtocol

在Hippy SDK中, 前端 `<Image>` 组件默认对应的 HippyImageView 会根据 source 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageViewCustomLoader` 协议。
在Hippy SDK中, 前端 `<Image>` 组件默认对应的 HippyImageView 会根据 src 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageCustomLoaderProtocol` 协议。

用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。
用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。注意该支持返回待解码的NSData类型图片数据,也支持直接返回解码后的UIImage图片,请根据需要选择合适方案。

```objectivec
@protocol HippyImageViewCustomLoader<HippyBridgeModule>
/// A Resource Loader for custom image loading
@protocol HippyImageCustomLoaderProtocol <HippyBridgeModule>

@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 <HippyImageViewCustomLoader>
@interface CustomImageLoader : NSObject <HippyImageCustomLoaderProtocol>
@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<HippyImageCustomLoaderProtocol>)imageLoader;
```

在上述实现代码中,我们使用了 `HIPPY_EXPORT_MODULE()` 宏来实现将此 ImageLoader 模块自动注册至 Hippy 框架中,框架内部将自动寻找实现了`HippyImageCustomLoaderProtocol` 协议的模块作为 ImageLoader。

PS: 若有多个模块实现 `HippyImageViewCustomLoader` 协议,系统只会使用其中一个作为默认 ImageLoader
!> 注意,同时只可有一个ImageLoader生效。若有多个模块实现了 `HippyImageCustomLoaderProtocol` 协议,框架使用最后一个作为生效的 ImageLoader。Hippy框架优先使用通过 `setCustomImageLoader:` 方法注册的ImageLoader。



Expand Down
66 changes: 66 additions & 0 deletions framework/ios/base/bridge/HippyBridge+Private.h
Original file line number Diff line number Diff line change
@@ -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 <memory>

class VFSUriLoader;
class NativeRenderManager;

namespace hippy {
inline namespace dom {
class DomManager;
class RootNode;
class RenderManager;
};
};


@protocol HippyBridgeInternal <NSObject>

/// The C++ version of RenderManager instance, bridge holds
@property (nonatomic, assign) std::shared_ptr<NativeRenderManager> renderManager;

/// URI Loader
@property (nonatomic, assign) std::weak_ptr<VFSUriLoader> vfsUriLoader;

@end


@interface HippyBridge (Private) <HippyBridgeInternal>

/**
* Set basic configuration for native render
* @param domManager DomManager
* @param rootNode RootNode
*/
- (void)setupDomManager:(std::shared_ptr<hippy::DomManager>)domManager
rootNode:(std::weak_ptr<hippy::RootNode>)rootNode;

@end



#endif /* HippyBridge_Private_h */
54 changes: 20 additions & 34 deletions framework/ios/base/bridge/HippyBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <memory>
#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

Expand Down Expand Up @@ -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> 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<HippyImageCustomLoaderProtocol> 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<HippyImageCustomLoaderProtocol>)imageLoader;

/**
* Image provider method
Expand All @@ -178,15 +177,8 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
- (void)addImageProviderClass:(Class<HippyImageProviderProtocol>)cls;
- (NSArray<Class<HippyImageProviderProtocol>> *)imageProviderClasses;

#ifdef __cplusplus
/**
* Set basic configuration for native render
* @param domManager DomManager
* @param rootNode RootNode
*/
- (void)setupDomManager:(std::shared_ptr<hippy::DomManager>)domManager
rootNode:(std::weak_ptr<hippy::RootNode>)rootNode;
#endif

#pragma mark -

/**
* Load instance for root view and show views
Expand All @@ -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<NativeRenderManager> renderManager;
#endif


/**
* JS invocation methods
*/
Expand Down
35 changes: 32 additions & 3 deletions framework/ios/base/bridge/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/

#import "HippyBridge.h"
#import "HippyBridge+Private.h"
#import "HippyBundleLoadOperation.h"
#import "HippyBundleExecutionOperation.h"
#import "HippyBundleOperationQueue.h"
Expand Down Expand Up @@ -173,6 +174,9 @@ @interface HippyBridge() {

@implementation HippyBridge

@synthesize renderManager = _renderManager;
@synthesize imageLoader = _imageLoader;

dispatch_queue_t HippyJSThread;

dispatch_queue_t HippyBridgeQueue() {
Expand Down Expand Up @@ -238,7 +242,7 @@ - (instancetype)initWithDelegate:(id<HippyBridgeDelegate>)delegate
[self setUp];

[self addImageProviderClass:[HippyDefaultImageProvider class]];
[self setVFSUriLoader:[self createURILoaderIfNeeded]];
[self setVfsUriLoader:[self createURILoaderIfNeeded]];
[self setUpNativeRenderManager];

[HippyBridge setCurrentBridge:self];
Expand Down Expand Up @@ -404,6 +408,31 @@ - (BOOL)moduleIsInitialized:(Class)moduleClass {
}


#pragma mark - Image Config Related

- (id<HippyImageCustomLoaderProtocol>)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<HippyImageCustomLoaderProtocol>)imageLoader {
@synchronized (self) {
if (imageLoader != _imageLoader) {
if (_imageLoader) {
HippyLogWarn(@"ImageLoader change from %@ to %@", _imageLoader, imageLoader);
}
_imageLoader = imageLoader;
}
}
}


#pragma mark - Debug Reload

- (void)reload {
Expand Down Expand Up @@ -607,7 +636,7 @@ - (void)rootViewSizeChangedEvent:(NSNumber *)tag params:(NSDictionary *)params {
[self sendEvent:@"onSizeChanged" params:dic];
}

- (void)setVFSUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
- (void)setVfsUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
[_javaScriptExecutor setUriLoader:uriLoader];
#ifdef ENABLE_INSPECTOR
auto devtools_data_source = _javaScriptExecutor.pScope->GetDevtoolsDataSource();
Expand All @@ -621,7 +650,7 @@ - (void)setVFSUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
#endif
}

- (std::weak_ptr<VFSUriLoader>)VFSUriLoader {
- (std::weak_ptr<VFSUriLoader>)vfsUriLoader {
return _uriLoader;
}

Expand Down
Loading

0 comments on commit 5e4e889

Please sign in to comment.