探究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 相关文章推荐
javascript new 需不需要继续使用
Jul 02 Javascript
单击和双击事件的冲突处理示例代码
Apr 03 Javascript
javascript实现的简单计时器
Jul 19 Javascript
jQuery自制提示框tooltip改进版
Aug 01 Javascript
jQuery模拟下拉框选择对应菜单的内容
Mar 07 Javascript
AngularJs实现聊天列表实时刷新功能
Jun 15 Javascript
Bootstrap treeview实现动态加载数据并添加快捷搜索功能
Jan 07 Javascript
vue环形进度条组件实例应用
Oct 10 Javascript
vue-mugen-scroll组件实现pc端滚动刷新
Aug 16 Javascript
Vue表单控件数据绑定方法详解
Feb 05 Javascript
微信小程序 button样式设置为图片的方法
Jun 19 Javascript
一行JavaScript代码如何实现瀑布流布局
Dec 11 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
正义联盟的终局之战《天启星战争》将成为DC动画宇宙的最后一部
2020/04/09 欧美动漫
php生成圆角图片的方法
2015/04/07 PHP
PHP基本语法实例总结
2016/09/09 PHP
php遍历目录下文件并按修改时间排序操作示例
2019/07/12 PHP
使用jquery与图片美化checkbox和radio控件的代码(打包下载)
2010/11/11 Javascript
JSONP 跨域共享信息
2012/08/16 Javascript
jquery之超简单的div显示和隐藏特效demo(分享)
2013/07/09 Javascript
jquery实现省市select下拉框的替换(示例代码)
2014/02/22 Javascript
理解JS绑定事件
2016/01/19 Javascript
JS常用正则表达式总结【经典】
2017/05/12 Javascript
AngularJS自定义指令之复制指令实现方法
2017/05/18 Javascript
在vue项目中,将juery设置为全局变量的方法
2018/09/25 Javascript
基于mpvue的简单弹窗组件mptoast使用详解
2019/08/02 Javascript
详解vue高级特性
2020/06/09 Javascript
浅谈vue 多个变量同时赋相同值互相影响
2020/08/05 Javascript
Vue实现导航栏菜单
2020/08/19 Javascript
vue3.0中友好使用antdv示例详解
2021/01/05 Vue.js
Python Trie树实现字典排序
2014/03/28 Python
python实现根据用户输入从电影网站获取影片信息的方法
2015/04/07 Python
Python闭包实现计数器的方法
2015/05/05 Python
Python3利用SMTP协议发送E-mail电子邮件的方法
2017/09/30 Python
浅谈python多进程共享变量Value的使用tips
2019/07/16 Python
解决python -m pip install --upgrade pip 升级不成功问题
2020/03/05 Python
pycharm 激活码及使用方式的详细教程
2020/05/12 Python
matplotlib基础绘图命令之bar的使用方法
2020/08/13 Python
canvas实现飞机打怪兽射击小游戏的示例代码
2018/07/09 HTML / CSS
北美三大旅游网站之一:Travelocity加拿大
2016/08/20 全球购物
美国体育用品商店:Academy Sports + Outdoors
2020/01/04 全球购物
信息工程学院毕业生推荐信
2013/11/05 职场文书
业务主管岗位职责
2013/11/20 职场文书
《列夫托尔斯泰》教学反思
2014/02/10 职场文书
公司管理建议书范文
2014/03/12 职场文书
房屋买卖委托书格式范本格式
2014/10/13 职场文书
离婚协议书范文2015
2015/01/26 职场文书
设备技术员岗位职责
2015/04/11 职场文书
风雨哈佛路观后感
2015/06/03 职场文书