探究react-native 源码的图片缓存问题
本文为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,依次得到下面结果:
可见,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自动生成的,非图片的磁盘缓存。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 详解vue.js之绑定class和style的示例代码
下一篇: jQuery实现广告条滚动效果