探究react-native 源码的图片缓存问题

本文为xcode模拟器测试,rn版本0.44.3

突然想学习下RN是如何封装ios中的UIImage的,看着看着发现图片的缓存问题是个坑。。。

先看js端图片使用的三种方式,依次排序1、2、3

<Image source={{uri:url}} style={{width:200,height:200}}/> // 1、 加载远程图片

<Image source={{uri:'1.png'}} style={{width:50,height:50}}/> //2、加载xcode中图片

<Image source={require('../../../Resources/Images/Contact/conact_searchIcon@3x.png')}/> //3、加载js中图片

1、2必须设置图片宽高,3不需设置。

对应的ios原生端文件是RCTImageViewManager,暴露的属性

RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);

就是js中Image组件的属性source,在js中设置source会触发该属性的setter方法。进入RCTImageView的

- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources

{

if (![imageSources isEqual:_imageSources]) {

_imageSources = [imageSources copy];

[self reloadImage];

}

}

通过此方法中断点打印imageSources,依次得到下面结果:

可见,Image组件加载图片都是采用URL的形式,将图片当作网络资源。不同的是URL的类型:

加载网络上图片 : http://

加载xcode资源 : file://

加载js中图片 : http://localhost:8081

追踪setter方法,到RCTImageLoader.m中的如下方法

- (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

{

__block volatile uint32_t cancelled = 0;

__block dispatch_block_t cancelLoad = nil;

dispatch_block_t cancellationBlock = ^{

dispatch_block_t cancelLoadLocal = cancelLoad;

if (cancelLoadLocal && !cancelled) {

cancelLoadLocal();

}

OSAtomicOr32Barrier(1, &cancelled);

};

// 下载图片完成后回调

__weak RCTImageLoader *weakSelf = self;

void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {

__typeof(self) spanSelf = weakSelf;

if (cancelled || !spanSelf) {

return;

}

// 如果imageOrData是图片类型,则直接回调

// 此处,如果是第二种情况,则会满足,其他情况继续走下面方法

if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {

cancelLoad = nil;

completionBlock(error, imageOrData);

return;

}

// 在内存中查看是否存在该url对应的字节码图片

if (cacheResult) {

UIImage *image = [[spanSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString

size:size

scale:scale

resizeMode:resizeMode

responseDate:fetchDate];

if (image) {

cancelLoad = nil;

completionBlock(nil, image);

return;

}

}

// 若没有缓存,则将图片解压,再将解压后图片缓存block

RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {

if (cacheResult && image) {

// Store decoded image in cache

[[spanSelf imageCache] addImageToCache:image

URL:imageURLRequest.URL.absoluteString

size:size

scale:scale

resizeMode:resizeMode

responseDate:fetchDate];

}

cancelLoad = nil;

completionBlock(error_, image);

};

// 具体的解压过程

cancelLoad = [spanSelf decodeImageData:imageOrData

size:size

scale:scale

clipped:clipped

resizeMode:resizeMode

completionBlock:decodeCompletionHandler];

};

// 走具体的方法加载图片,1、3种情况用网络请求下载,2情况加载本地文件

cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest

size:size

scale:scale

resizeMode:resizeMode

progressBlock:progressBlock

partialLoadBlock:partialLoadBlock

completionBlock:completionHandler];

return cancellationBlock;

}

具体的缓存类是RCTImageCache,采用NSCache缓存,方法

- (void)addImageToCache:(UIImage *)image

forKey:(NSString *)cacheKey

{

if (!image) {

return;

}

CGFloat bytes = image.size.width * image.size.height * image.scale * image.scale * 4;

if (bytes <= RCTMaxCachableDecodedImageSizeInBytes) {

[self->_decodedImageCache setObject:image

forKey:cacheKey

cost:bytes];

}

}

RCTMaxCachableDecodedImageSizeInBytes是个常量,为1048576,也就是只缓存小于1MB的图片。

问题出在cacheKey,查看缓存key的方法

static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale,

RCTResizeMode resizeMode, NSString *responseDate)

{

return [NSString stringWithFormat:@"%@|%g|%g|%g|%zd|%@",

imageTag, size.width, size.height, scale, resizeMode, responseDate];

}

缓存key的生成方法中包含了responseDate,responseDate是网络请求时返回来的

responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];


1、3方式每次加载都是一个网络请求,那么网络请求的时间总是变化的,于是responseDate是变化的,cacheKey不唯一,所以虽然系统做了图片的缓存,但是每次取出的都为nil,缓存无效。

2方式加载具体方法在RCTLocalAssetImageLoader.m中,其调用的是RCTUtils的RCTImageFromLocalAssetURL方法

UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)

{

// .....省略各种处理

UIImage *image = nil;

if (bundle) {

image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];

} else {

image = [UIImage imageNamed:imageName];

}

// .....省略各种处理

return image;

}

可见是采用[UIImage imageNamed:imageName]的方式加载xcode自带的图片,这个是有内存缓存的。

综上,对react-native图片加载

1、3情况,没有内存缓存

2情况有系统默认的内存缓存

所有情况都没有磁盘缓存

想让内存缓存生效,只需要改变cacheKey的生成规则即可。

补充:沙盒下面的Library/Caches/项目bunderId号/fsCachedData文件夹里面会磁盘缓存大于一定值(测试约为5kb)的图片和文件,这个是NSURLSession网络请求系统默认的缓存类NSURLCache自动生成的,非图片的磁盘缓存。

以上是 探究react-native 源码的图片缓存问题 的全部内容, 来源链接: utcz.com/z/333504.html

回到顶部