RN圖片加載和原生統(tǒng)一
針對RN和原生混合開發(fā)的項(xiàng)目,由于圖片的加載RN有自己的一套機(jī)制,跟原生的是分開的,就存在加載和緩存的差異性;我們可以做一些工作讓圖片的加載統(tǒng)一成一套,這對于維護(hù)和做一些優(yōu)化都是有益處的
1. RN圖片加載框架
在對RN的圖片加載做優(yōu)化之前,我們得先知道RN的圖片加載框架流程;
RN的圖片的加載、緩存、解碼等都是在RCTImageLoader中處理的,這里大概梳理了下其中的結(jié)構(gòu)

RCTImageLoader可以劃分為以下幾個(gè)模塊:
- 圖片加載 RCTImageURLLoader
- 圖片緩存 RCTImageCache
- 圖片解碼 RCTImageDataDecoder
為了增強(qiáng)模塊的可擴(kuò)展性,RN將這三個(gè)核心模塊都提供了外部可定制的能力,通過set方法、或者協(xié)議的方式;
1.1 圖片的緩存
定義了RCTImageCache協(xié)議,同時(shí)提供了- (void)setImageCache:(id<RCTImageCache>)cache;來供外部去定義圖片緩存模塊
/**
* Provides an interface to use for providing a image caching strategy.
*/
@protocol RCTImageCache <NSObject>
- (UIImage *)imageForUrl:(NSString *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode;
- (void)addImageToCache:(UIImage *)image
URL:(NSString *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
response:(NSURLResponse *)response;
@end
如果我們設(shè)置了自定義的圖片緩存,那么就使用自定義的,否則RN內(nèi)部會使用默認(rèn)的RCTImageCache;這個(gè)默認(rèn)的實(shí)現(xiàn)只做了內(nèi)存緩存,沒有做磁盤緩存的
// 提供set方法供外部設(shè)置圖片緩存模塊
- (void)setImageCache:(id<RCTImageCache>)cache;
- (void)setImageCache:(id<RCTImageCache>)cache
{
if (_imageCache) {
RCTLogWarn(@"RCTImageCache was already set and has now been overriden.");
}
_imageCache = cache;
}
// 如果設(shè)置了圖片緩存模塊則用外部設(shè)置的,否則使用默認(rèn)實(shí)現(xiàn)
- (id<RCTImageCache>)imageCache
{
if (!_imageCache) {
//set up with default cache
_imageCache = [RCTImageCache new];
}
return _imageCache;
}
1.2 圖片加載
圖片加載的統(tǒng)一入口函數(shù):
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
同時(shí)定義了RCTImageURLLoader協(xié)議,將圖片的加載能力進(jìn)行抽象;協(xié)議中的canLoadImageURL:方法用來定義該Loader支持的URL資源的加載,這樣不同的圖片資源就可以使用不同的Loader去加載
/**
* Provides the interface needed to register an image loader. Image data
* loaders are also bridge modules, so should be registered using
* RCT_EXPORT_MODULE().
*/
@protocol RCTImageURLLoader <RCTBridgeModule>
// 是否是該loader支持加載的圖片URL
- (BOOL)canLoadImageURL:(NSURL *)requestURL;
// 圖片加載入口函數(shù)
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
// loader 優(yōu)先級
- (float)loaderPriority;
// 是否需要將任務(wù)放到內(nèi)部的串行隊(duì)列去執(zhí)行,默認(rèn)是YES在主線程執(zhí)行
- (BOOL)requiresScheduling;
// 是否緩存圖片,默認(rèn)是YES
- (BOOL)shouldCacheLoadedImages;
@end
跟RCTImageCache不同的是,圖片加載loader不是通過提供set接口去定制,而是通過RCT_EXPORT_MODULE()的方式導(dǎo)出模塊給RN,內(nèi)部去獲取實(shí)現(xiàn)了RCTImageURLLoader協(xié)議的loaders列表_loaders = [_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)]
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
if (!_maxConcurrentLoadingTasks) {
[self setUp];
}
if (!_loaders) {
// Get loaders, sorted in reverse priority order (highest priority first)
RCTAssert(_bridge, @"Bridge not set");
_loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
}
// ...
// Normal code path
for (id<RCTImageURLLoader> loader in _loaders) {
if ([loader canLoadImageURL:URL]) {
return loader;
}
}
return nil;
}
RN圖片加載模塊默認(rèn)實(shí)現(xiàn)了2類資源的Loader
- RCTPhotoLibraryImageLoader 相冊資源
- RCTLocalAssetImageLoader 本地資源
對于網(wǎng)絡(luò)圖片的加載,沒有內(nèi)置的Loader,假如外部沒有定義該類型的loader則默認(rèn)走的RCTNetworking模塊去加載的
我們?nèi)绻雽τ诰W(wǎng)絡(luò)資源圖片的加載走跟原生一樣的模塊(比如SDWebImage)則可以通過2種方式去實(shí)現(xiàn):
- 可以通過定義一個(gè)Loader并通過
RCT_EXPORT_MODULE()的方式導(dǎo)出模塊給RN就能實(shí)現(xiàn) - hook內(nèi)部的默認(rèn)網(wǎng)絡(luò)資源加載函數(shù)
loadImageWithURLRequest:
1.3 圖片解碼
針對請求返回的是data類型的數(shù)據(jù),則需要去將data解碼成圖片;通過定義了RCTGIFImageDecoder協(xié)議來將解碼的能力抽象,外部則可以實(shí)現(xiàn)對應(yīng)的解碼器來解碼不同類型的數(shù)據(jù),這里的設(shè)計(jì)跟上面介紹的Loader的設(shè)計(jì)是一樣的
/**
* Provides the interface needed to register an image decoder. Image decoders
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
*/
@protocol RCTImageDataDecoder <RCTBridgeModule>
// 判斷數(shù)據(jù)是否是該loader可以解碼的
- (BOOL)canDecodeImageData:(NSData *)imageData;
// 解碼函數(shù),傳入imageData解碼得到image
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
// 優(yōu)先級,內(nèi)部會根據(jù)這個(gè)來排序,使用優(yōu)先級高的decoder
- (float)decoderPriority;
@end
RN模塊默認(rèn)內(nèi)置了一個(gè)RCTGIFImageDecodergif的解碼模塊,假如我們需要支持WebP,那么就可以定義一個(gè)WebP的Decoder來實(shí)現(xiàn)解碼
2. RN和原生的圖片緩存統(tǒng)一
由于RN和原生的圖片加載是2個(gè)模塊去實(shí)現(xiàn)的,這就存在一張圖可能RN側(cè)加載緩存了、原生側(cè)也加載緩存了,這就造成了資源的重復(fù)加載以及無法復(fù)用緩存的問題;
同時(shí)RN的緩存模塊還只是做了內(nèi)存緩存的,app殺掉下次打開則還是又會發(fā)起網(wǎng)絡(luò)請求去加載,這就造成了不必要的請求浪費(fèi)
為了解決這些問題,將兩端的圖片緩存統(tǒng)一就顯得有必要,同時(shí)統(tǒng)一了之后,后續(xù)需要做修改或者優(yōu)化則不用2端都去修改,增強(qiáng)了可維護(hù)性
2.1 自定義ImageCache
RCTImageLoader也提供了緩存協(xié)議以及設(shè)置緩存的函數(shù),自定制起來也很簡單,定義一個(gè)Cache實(shí)現(xiàn)RCTImageCache協(xié)議
@interface HCRNImageCache : NSObject <RCTImageCache>
@end
項(xiàng)目用的是SDWebImage做圖片加載的,那么緩存的內(nèi)部實(shí)現(xiàn)就是用SD那一套,只需要將緩存的key跟SD保持一致,那么讀取和寫入就統(tǒng)一了
@implementation HCRNImageCache
#pragma mark - RCTImageCache
- (void)addImageToCache:(UIImage *)image URL:(NSString *)url size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode response:(NSURLResponse *)response {
NSString *cacheKey = [HCImageLoaderUtility cacheKeyWithUrlString:url size:size scale:scale];
if ([self isURLInBlackList:url]) {
[[SDImageCache sharedImageCache] storeImage:image forKey:cacheKey toDisk:NO completion:nil];
} else {
[[SDImageCache sharedImageCache] storeImage:image forKey:cacheKey toDisk:YES completion:nil];
}
}
- (UIImage *)imageForUrl:(NSString *)url size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode {
NSString *cacheKey = [HCImageLoaderUtility cacheKeyWithUrlString:url size:size scale:scale];
if ([self isURLInBlackList:url]) {
return [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:cacheKey];
} else {
return [[SDImageCache sharedImageCache] imageFromCacheForKey:cacheKey];
}
}
#pragma mark - Private Methods
// 這里用來判斷哪些資源不是網(wǎng)絡(luò)資源:比如本地資源或者RN調(diào)試模式的本地資源
- (BOOL)isURLInBlackList:(NSString *)url {
return [HCImageLoaderUtility isLocalAssetURL:url];
}
@end
這里我將用到的一些工具方法抽到一個(gè)工具類中
// 由于有一些是工具方法,就抽出來一個(gè)工具類來
@implementation HCImageLoaderUtility
// 根據(jù)URL返回緩存的key,內(nèi)部實(shí)現(xiàn)就是SD那一套
+ (NSString *)cacheKeyWithUrlString:(NSString *)urlString size:(CGSize)size scale:(CGFloat)scale {
NSString *cacheKey = [self sdCacheKeyWithUrlString:urlString];
return cacheKey;
}
+ (NSString *)sdCacheKeyWithUrlString:(NSString *)urlString {
NSString *cacheKey = [[SDWebImageManager sharedManager] cacheKeyForURL:[NSURL URLWithString:urlString]];
return cacheKey;
}
// 判斷URL是否是本地的資源文件
+ (BOOL)isLocalAssetURL:(NSString *)url {
// 調(diào)試RN模式
BOOL isDebugMode = [url rangeOfString:@"http"].location != NSNotFound && [url rangeOfString:@"8081/assets"].location != NSNotFound;
// 加載本地圖片文件
BOOL isLocalAsset = [url rangeOfString:@"file://"].location != NSNotFound;
return isDebugMode || isLocalAsset;
}
+ (BOOL)isURLNotSupportFormat:(NSString *)url {
// 非阿里云返回原地址; 動(dòng)圖返回原地址
if (![url containsString:@"oss"] || ![url containsString:@"aliyuncs.com"] || [url containsString:@".gif"]) {
return YES;
}
return NO;
}
+ (BOOL)checkNeedSetSizeCompressFormatWithURLString:(NSString *)url size:(CGSize)size scale:(CGFloat)scale {
CGFloat width = size.width * scale;
CGFloat height = size.height * scale;
BOOL willSetSize = YES;
if (width <= 0 || width > 4096 || height <= 0 || height > 4096) {
willSetSize = NO;
}
return willSetSize;
}
@end
2.2 注入自定義ImageCache到RN模塊
注入則需要注意注入的時(shí)機(jī),以及當(dāng)bridge reload之后需要重新注入(reload之后會重新加載RN模塊,RN模塊load完成會有一個(gè)通知RCTJavaScriptDidLoadNotification);這里我們使用一個(gè)管理類來處理注入的邏輯,監(jiān)聽這個(gè)RN模塊load完成的通知,然后去設(shè)置自定義的ImageCache即可
@interface HCRNImageLoader ()
@property (nonatomic, nullable, strong) HCRNImageCache *imageCache;
@end
@implementation HCRNImageLoader
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
+ (instancetype)sharedInstance {
static HCRNImageLoader *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [self new];
});
return _instance;
}
- (void)startObserver {
[self addNotifications];
}
- (void)registerHCImageLoader {
// 你的項(xiàng)目的RCTbridge實(shí)例
SomeBridge.imageLoader setImageCache:self.imageCache];
}
- (void)unregisterHCImageLoader {
self.imageCache = nil;
SomeBridge.imageLoader setImageCache:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Private Methods
- (void)addNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jsBridgeReloaded) name:RCTJavaScriptDidLoadNotification object:nil];
}
- (void)jsBridgeReloaded {
[self registerHCImageLoader];
}
#pragma mark - Getter && Setter
- (HCRNImageCache *)imageCache {
if (_imageCache == nil) {
_imageCache = [HCRNImageCache new];
}
return _imageCache;
}
@end
3. RN和原生的圖片加載統(tǒng)一
圖片的加載統(tǒng)一上面也介紹了有2種方式可以實(shí)現(xiàn),自定義RCTImageURLLoader或者h(yuǎn)ook RN的圖片加載入口函數(shù)loadImageWithURLRequest:
下面介紹定義loader的方式:
@implementation HCRNURLImageLoader
RCT_EXPORT_MODULE()
- (BOOL)canLoadImageURL:(NSURL *)requestURL {
// 這里的邏輯實(shí)現(xiàn)根據(jù)項(xiàng)目的圖片資源的URL格式來實(shí)現(xiàn)即可
return (!requestURL.isFileURL && [requestURL.absoluteString containsString:@"https://"]);
}
- (BOOL)shouldCacheLoadedImages {
return YES;
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler {
// 取緩存
NSString *cacheKey = [HCRNImageLoaderUtility cacheKeyWithUrlString:imageURL.absoluteString size:size scale:scale];
UIImage *cachedImage = [[SDImageCache sharedImageCache] imageFromCacheForKey:cacheKey];
if (cachedImage) {
if (completionHandler) {
completionHandler(nil, cachedImage);
}
} else {
// 取網(wǎng)絡(luò)
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (progressHandler) {
progressHandler(receivedSize, expectedSize);
}
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
if (image) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[SDImageCache sharedImageCache] storeImage:image forKey:cacheKey completion:nil];
});
}
if (completionHandler) {
completionHandler(error, image);
}
}];
}
return ^{};
}
需要注意的是如果自己實(shí)現(xiàn)了Loader,則對應(yīng)資源的加載直接托管給Loader去實(shí)現(xiàn)了,我們需要在loader中處理緩存、解碼等這一套流程
RCTImageLoader代碼片段截取
if (loadHandler) {
cancelLoad = [loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
} else {
UIImage *image;
if (cacheResult) {
image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode];
}
if (image) {
completionHandler(nil, image, nil);
} else {
// Use networking module to load image
cancelLoad = [strongSelf _loadURLRequest:request
progressBlock:progressHandler
completionBlock:completionHandler];
}
}
你可以選擇這種方式定義一個(gè)Loader去處理,也可以不做處理讓走默認(rèn)的圖片加載流程
4. 圖片加載的一些優(yōu)化
圖片加載優(yōu)化除了緩存之外,還包括按需加載(按視圖尺寸加載)、壓縮(壓縮參數(shù)、WebP格式等等)、裁剪(圓角)等等,現(xiàn)在主流的文件托管平臺都支持通過配置參數(shù)來獲取定制化的圖片資源
這些通過定制URL的圖片格式化參數(shù)來加載圖片帶來的益處是值得去做的
- 按視圖尺寸加載 -- 減少了視圖渲染的
Color Misaligned Images - 壓縮參數(shù) -- 降低了圖片資源的大小,提升了下載的效率
- 裁剪等參數(shù) -- 不需要代碼去處理特殊的效果,通常一些效果還會觸發(fā)離屏渲染
項(xiàng)目中圖片是放在阿里云OSS上,我們可以根據(jù)文檔來設(shè)置這些參數(shù)圖片縮放
那么想讓RN的圖片加載也可以去設(shè)置圖片處理的參數(shù),當(dāng)然也可以在自定義的Loader中去處理URL拼接format參數(shù),或者不自定義Loader的話直接hook RN圖片加載的入口函數(shù)loadImageWithURLRequest:
@implementation RCTImageLoader (HCLoader)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:partialLoadBlock:completionBlock:) with:@selector(hc_loadImageWithURLRequest:size:scale:clipped:resizeMode:progressBlock:partialLoadBlock:completionBlock:)];
});
}
- (RCTImageLoaderCancellationBlock)hc_loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock {
NSString *formattedURLString = [self formatURLWithURLString:imageURLRequest.URL.absoluteString size:size scale:scale mode:resizeMode];
NSMutableURLRequest *tmpURLRequest = imageURLRequest.mutableCopy;
tmpURLRequest.URL = [NSURL URLWithString:formattedURLString];
return [self hc_loadImageWithURLRequest:tmpURLRequest
size:size
scale:scale
clipped:clipped
resizeMode:resizeMode
progressBlock:progressBlock
partialLoadBlock:partialLoadBlock
completionBlock:completionBlock];
}
- (NSString *)formatURLWithURLString:(NSString *)urlString size:(CGSize)size scale:(CGFloat)scale mode:(RCTResizeMode)resizeMode {
BOOL isInBlackList = [self isURLInBlackList:urlString];
if (isInBlackList) { // 不需要拼接參數(shù)的直接返回原URL
return urlString;
}
// 根據(jù)傳入的width、height、scale、resizeMode來拼接url的format參數(shù),格式類似這樣 ?x-oss-process=image/resize,m_lfit,w_148,h_148/format,webp
BOOL needSetSizeFormat = [HCImageLoaderUtility checkNeedSetSizeCompressFormatWithURLString:urlString size:size scale:scale];
// ?x-oss-process=image/resize,m_lfit,w_148,h_148/format,webp
NSString *resizeModeString = @"lfit"; // oss默認(rèn)值
switch (resizeMode) {
case RCTResizeModeCover: // UIViewContentModeScaleAspectFill
resizeModeString = @"fill";
break;
case RCTResizeModeContain: // UIViewContentModeScaleAspectFit
resizeModeString = @"pad";
break;
case RCTResizeModeStretch: // UIViewContentModeScaleToFill
resizeModeString = @"fixed";
break;
case RCTResizeModeCenter: // UIViewContentModeCenter
resizeModeString = @"fill";
break;
default:
resizeModeString = @"lfit";
break;
}
NSMutableString *formattedString = urlString.mutableCopy;
// 縮放配置
[formattedString appendFormat:@"?x-oss-process=image/resize,m_%@", resizeModeString];
if (needSetSizeFormat) {
// 寬高設(shè)置
[formattedString appendFormat:@",w_%.0f,h_%.0f", ceil(size.width * scale), ceil(size.height * scale)];
}
// WebP格式設(shè)置
[formattedString appendFormat:@"/format,webp"];
return formattedString;
}
// 這里根據(jù)需求,將不支持參數(shù)的URL、或者不需要拼接參數(shù)的URL過濾掉
- (BOOL)isURLInBlackList:(NSString *)url {
BOOL isLocalAsset = [HCImageLoaderUtility isLocalAssetURL:url];
BOOL isNotSupportFormat = [HCImageLoaderUtility isURLNotSupportFormat:url];
return isLocalAsset || isNotSupportFormat;
}
@end
我們設(shè)置了圖片格式WebP,那么默認(rèn)的RCTImageLoader是么有WebP格式的Decoder,此時(shí)就需要實(shí)現(xiàn)一個(gè)供其使用,定義一個(gè)實(shí)現(xiàn)協(xié)議RCTImageDataDecoder的解碼器
@interface HCRNWebPImageDecoder : NSObject <RCTImageDataDecoder>
@end
@implementation HCRNWebPImageDecoder
RCT_EXPORT_MODULE()
- (BOOL)canDecodeImageData:(NSData *)imageData {
return [[SDWebImageWebPCoder sharedCoder] canDecodeFromData:imageData];
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler {
UIImage *image = [[SDWebImageWebPCoder sharedCoder] decodedImageWithData:imageData];
if (completionHandler) {
if (image) {
completionHandler(nil, image);
} else {
completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{NSLocalizedFailureReasonErrorKey : @"解碼失敗"}], nil);
}
}
return ^{};
}
@end
至此RN的圖片加載就也支持設(shè)置format參數(shù)獲取處理過的圖片,同時(shí)也支持WebP格式的圖片的加載了
5. 總結(jié)
在做RN的圖片加載模塊優(yōu)化的時(shí)候,閱讀了RCTImageLoader的源碼,也學(xué)到了一些設(shè)計(jì)的理念,模塊劃分很清晰:Cache、Loader、Decoder;同時(shí)也提供了接口或者協(xié)議的方式將能力抽象,供外部可定制化。這樣在做定制的時(shí)候就很清晰,而不用去各種hook方法去實(shí)現(xiàn)。