探究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 相关文章推荐
WordPress 插件——CoolCode使用方法与下载
Jul 02 Javascript
基于jquery的回到页面顶部按钮
Jun 27 Javascript
js前台判断开始时间是否小于结束时间
Feb 23 Javascript
JS无法捕获滚动条上的mouse up事件的原因猜想
Mar 21 Javascript
javascript对talbe进行动态添加、删除、验证实现代码
Mar 29 Javascript
jQuery.Callbacks()回调函数队列用法详解
Jun 14 Javascript
详解redux异步操作实践
Aug 15 Javascript
Angular6新特性之Angular Material
Dec 28 Javascript
Element-Ui组件 NavMenu 导航菜单的具体使用
Oct 24 Javascript
echarts饼图各个板块之间的空隙如何实现
Dec 01 Javascript
原生JS封装vue Tab切换效果
Apr 28 Vue.js
Vue3如何理解ref toRef和toRefs的区别
Feb 18 Vue.js
详解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数据库密码的找回的步骤
2011/01/12 PHP
有关PHP中MVC的开发经验分享
2012/05/17 PHP
怎样给PHP源代码加密?PHP二进制加密与解密的解决办法
2013/04/22 PHP
PHP 只允许指定IP访问(允许*号通配符过滤IP)
2014/07/08 PHP
php+ajax实时刷新简单实例
2015/02/25 PHP
PHP定时任务获取微信access_token的方法
2016/10/10 PHP
轻松实现php文件上传功能
2017/02/17 PHP
PHP从尾到头打印链表实例讲解
2018/09/27 PHP
JavaScript 用Node.js写Shell脚本[译]
2012/09/20 Javascript
jquery高效反选具体实现
2013/05/05 Javascript
jQuery中slideUp()方法用法分析
2014/12/24 Javascript
jQuery旋转木马式幻灯片轮播特效
2015/12/04 Javascript
Angularjs整合微信UI(weui)
2016/03/15 Javascript
纯JS代码实现一键分享功能
2016/04/20 Javascript
js实现String.Fomat的实例代码
2016/09/02 Javascript
JS继承之借用构造函数继承和组合继承
2016/09/07 Javascript
JS制作图形验证码实现代码
2020/10/19 Javascript
关于Vue.js一些问题和思考学习笔记(2)
2016/12/02 Javascript
react中使用swiper的具体方法
2018/05/15 Javascript
javascript 易错知识点实例小结
2020/04/25 Javascript
Vue 3.0中jsx语法的使用
2020/11/13 Javascript
全面理解Python中self的用法
2016/06/04 Python
Python实现抓取网页生成Excel文件的方法示例
2017/08/05 Python
Python通过Pygame绘制移动的矩形实例代码
2018/01/03 Python
Python中跳台阶、变态跳台阶与矩形覆盖问题的解决方法
2018/05/19 Python
python数据结构学习之实现线性表的顺序
2018/09/28 Python
详解Python with/as使用说明
2018/12/13 Python
python Kmeans算法原理深入解析
2019/08/23 Python
Python SELENIUM上传文件或图片实现过程
2019/10/28 Python
python报错TypeError: ‘NoneType‘ object is not subscriptable的解决方法
2020/11/05 Python
纽约和芝加哥当天送花:Ode à la Rose
2019/07/05 全球购物
污水厂厂长岗位职责
2014/01/04 职场文书
小学生期末评语
2014/04/21 职场文书
2014年学校领导班子对照检查材料
2014/09/19 职场文书
防灾减灾标语
2014/10/07 职场文书
2014超市收银员工作总结
2014/11/13 职场文书