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


Posted in Javascript onAugust 24, 2017

本文为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,依次得到下面结果:

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

可见,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) strongSelf = weakSelf;
 if (cancelled || !strongSelf) {
  return;
 }
  // 如果imageOrData是图片类型,则直接回调
  // 此处,如果是第二种情况,则会满足,其他情况继续走下面方法
 if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
  cancelLoad = nil;
  completionBlock(error, imageOrData);
  return;
 }
 
 // 在内存中查看是否存在该url对应的字节码图片
 if (cacheResult) {
  UIImage *image = [[strongSelf 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
  [[strongSelf imageCache] addImageToCache:image
            URL:imageURLRequest.URL.absoluteString
           size:size
           scale:scale
          resizeMode:resizeMode
         responseDate:fetchDate];
  }

  cancelLoad = nil;
  completionBlock(error_, image);
 };
  // 具体的解压过程
 cancelLoad = [strongSelf 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自动生成的,非图片的磁盘缓存。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JSONP获取Twitter和Facebook文章数的具体步骤
Feb 24 Javascript
jQuery学习笔记之jQuery原型属性和方法
Jun 09 Javascript
javascript动态创建表格及添加数据实例详解
May 13 Javascript
js实现不提交表单获取单选按钮值的方法
Aug 21 Javascript
js文字横向滚动特效
Nov 11 Javascript
下雪了 javascript实现雪花飞舞
Aug 02 Javascript
详解javascript获取url信息的常见方法
Dec 19 Javascript
BootStrap注意事项小结(五)表单
Mar 10 Javascript
React Native之TextInput组件解析示例
Aug 22 Javascript
jQuery实现动态显示select下拉列表数据的方法
Feb 05 jQuery
JS扩展String.prototype.format字符串拼接的功能
Mar 09 Javascript
Element el-button 按钮组件的使用详解
Feb 01 Javascript
详解vue.js之绑定class和style的示例代码
Aug 24 #Javascript
10个最优秀的Node.js MVC框架
Aug 24 #Javascript
Vue.js实现输入框绑定的实例代码
Aug 24 #Javascript
Vue.js实现价格计算器功能
Mar 30 #Javascript
js微信分享实现代码
Oct 11 #Javascript
Vue.js实现实例搜索应用功能详细代码
Aug 24 #Javascript
实例详解JSON取值(key是中文或者数字)方式
Aug 24 #Javascript
You might like
利用php绘制饼状图的实现代码
2013/06/07 PHP
ThinkPHP3.1新特性之动态设置自动完成及自动验证示例代码
2014/06/23 PHP
php删除左端与右端空格的方法
2014/11/29 PHP
PHP中的异常处理机制深入讲解
2020/11/10 PHP
Webkit的跨域安全问题说明
2011/09/13 Javascript
jQuery 下拉列表 二级联动插件分享
2012/03/29 Javascript
jquery.qrcode在线生成二维码使用示例
2013/08/21 Javascript
javaScript对文字按照拼音排序实现代码
2013/12/27 Javascript
JavaScript实现穷举排列(permutation)算法谜题解答
2014/12/29 Javascript
jQuery实现内容定时切换效果完整实例
2016/04/06 Javascript
微信小程序 wxapp画布 canvas详细介绍
2016/10/31 Javascript
vue的无缝滚动组件vue-seamless-scroll实例
2017/12/18 Javascript
vue如何限制只能输入正负数及小数
2019/07/04 Javascript
如何使用 vue-cli 创建模板项目
2020/11/19 Vue.js
[02:36]DOTA2混沌骑士 英雄基础教程
2013/11/26 DOTA
Phantomjs抓取渲染JS后的网页(Python代码)
2016/05/13 Python
python开发简易版在线音乐播放器
2017/03/03 Python
Python书单 不将就
2017/07/11 Python
详解Python if-elif-else知识点
2018/06/11 Python
不知道这5种下划线的含义,你就不算真的会Python!
2018/10/09 Python
在tensorflow中设置使用某一块GPU、多GPU、CPU的操作
2020/02/07 Python
Python selenium自动化测试模型图解
2020/04/15 Python
PyQt5实现登录页面
2020/05/30 Python
优衣库美国官网:UNIQLO美国
2018/04/14 全球购物
最新的咖啡店创业计划书
2013/12/30 职场文书
行政专员岗位职责
2014/01/02 职场文书
给朋友的道歉信
2014/01/09 职场文书
爱情保证书范文
2014/02/01 职场文书
数控技术专业毕业自荐书范文
2014/02/05 职场文书
关于是否需要写商业计划书
2014/02/07 职场文书
打架检讨书2000字
2014/02/22 职场文书
卫生院艾滋病宣传活动小结
2014/07/09 职场文书
小学生校园广播稿
2014/09/28 职场文书
2016大学生诚信考试承诺书
2016/03/25 职场文书
Appium中scroll和drag_and_drop根据元素位置滑动
2022/02/15 Python
canvas 中如何实现物体的框选
2022/08/05 Javascript