探究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 相关文章推荐
this[] 指的是什么内容 讨论
Mar 24 Javascript
JavaScript中的对象化编程
Jan 16 Javascript
JavaScript的单例模式 (singleton in Javascript)
Jun 11 Javascript
基于jquery的bankInput银行卡账号格式化
Aug 22 Javascript
js触发asp.net的Button的Onclick事件应用
Feb 02 Javascript
用js设置下拉框为只读的小技巧
Apr 10 Javascript
JavaScript中的函数嵌套使用
Jun 04 Javascript
Highcharts使用简例及异步动态读取数据
Dec 30 Javascript
Vue filters过滤器的使用方法
Jul 14 Javascript
js导出Excel表格超出26位英文字符的解决方法ES6
Nov 15 Javascript
vue+element 模态框表格形式的可编辑表单实现
Jun 07 Javascript
基于 Vue 的 Electron 项目搭建过程图文详解
Jul 22 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 数组基础知识小结
2010/08/20 PHP
Laravel执行migrate命令提示:No such file or directory的解决方法
2016/03/16 PHP
js 页面传参数时 参数值含特殊字符的问题
2009/12/13 Javascript
jQuery基础知识filter()和find()实例说明
2010/07/06 Javascript
js将字符串转成正则表达式的实现方法
2013/11/13 Javascript
jQuery实现当前页面标签高亮显示的方法
2015/03/10 Javascript
Angularjs使用directive自定义指令实现attribute继承的方法详解
2016/08/05 Javascript
jquery validate表单验证插件
2016/09/06 Javascript
JavaScript在form表单中使用button按钮实现submit提交方法
2017/01/23 Javascript
基于vue.js实现侧边菜单栏
2017/03/20 Javascript
node.js平台下利用cookie实现记住密码登陆(Express+Ejs+Mysql)
2017/04/26 Javascript
JS表单提交验证、input(type=number) 去三角 刷新验证码
2017/06/21 Javascript
template.js前端模板引擎使用详解
2017/10/10 Javascript
bootstrap轮播模板使用方法详解
2017/11/17 Javascript
详解开发react应用最好用的脚手架 create-react-app
2018/04/24 Javascript
Vue使用lodop实现打印小结
2019/07/06 Javascript
修改NPM全局模式的默认安装路径的方法
2020/12/15 Javascript
详解pandas库pd.read_excel操作读取excel文件参数整理与实例
2019/02/17 Python
Python3.4解释器用法简单示例
2019/03/22 Python
django连接oracle时setting 配置方法
2019/08/29 Python
Python程序暂停的正常处理方法
2019/11/07 Python
解决Python列表字符不区分大小写的问题
2019/12/19 Python
捷克体育用品购物网站:D-sport
2017/12/28 全球购物
波兰家居和花园家具专家:4Home
2019/05/26 全球购物
美国名表在线商城:Ashford(支持中文)
2019/09/24 全球购物
写演讲稿所需要注意的4个条件
2014/01/09 职场文书
酒店中秋节促销方案
2014/01/30 职场文书
护理专业求职信
2014/06/15 职场文书
幼儿园门卫岗位职责范本
2014/07/02 职场文书
2015年乡镇残联工作总结
2015/05/13 职场文书
甲午大海战观后感
2015/06/02 职场文书
公司借款担保书
2015/09/22 职场文书
2016大学生诚信考试承诺书
2016/03/25 职场文书
Django项目配置Memcached和Redis, 缓存选择哪个更有优势
2021/04/06 Python
「回转企鹅罐」10周年纪念展「輪るピングドラム展」海报公开
2022/03/22 日漫
mysql查找连续出现n次以上的数字
2022/05/11 MySQL