RN圖片加載和原生統(tǒng)一

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.jpg

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):

  1. 可以通過定義一個(gè)Loader并通過RCT_EXPORT_MODULE()的方式導(dǎo)出模塊給RN就能實(shí)現(xiàn)
  2. 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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容