欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-29 08:10:44
本文为xcode模拟器测试,rn版本0.44.3 突然想学习下rn是如何封装ios中的uiimage的,看着看着发现图片的缓存问题是个坑。。。 先看js端图片使...

本文为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自动生成的,非图片的磁盘缓存。

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