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

发布时间 - 2026-01-11 02:56:57    点击率:

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

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


# react  # native图片缓存  # reactnative  # 图片缓存  # React 源码调试方式  # react 源码中位运算符的使用详解  # 使用VScode 插件debugger for chrome 调试react源码的方法  # 浅谈react-router@4.0 使用方法和源码分析  # 用React-Native+Mobx做一个迷你水果商城APP(附源码)  # 深入理解react-router@4.0 使用和源码解析  # React之echarts-for-react源码解读  # 加载  # 的是  # 都是  # 看着  # 是个  # 回调  # 是有  # 三种  # 只需要  # 不需  # 想让  # 自带  # 约为  # 都为  # 则会  # 再将  # 第二种  # 先看  # 自动生成  # 出在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】  Linux系统命令中tree命令详解  php json中文编码为null的解决办法  如何快速完成中国万网建站详细流程?  Laravel如何发送系统通知?(Notification渠道示例)  最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?  Win11怎样安装网易有道词典_Win11安装词典教程【步骤】  高性价比服务器租赁——企业级配置与24小时运维服务  Laravel Debugbar怎么安装_Laravel调试工具栏配置指南  如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  如何在万网利用已有域名快速建站?  Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】  移动端脚本框架Hammer.js  Linux网络带宽限制_tc配置实践解析【教程】  Laravel如何升级到最新版本?(升级指南和步骤)  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  UC浏览器如何设置启动页 UC浏览器启动页设置方法  谷歌Google入口永久地址_Google搜索引擎官网首页永久入口  html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】  如何用y主机助手快速搭建网站?  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  Python自动化办公教程_ExcelWordPDF批量处理案例  如何用JavaScript实现文本编辑器_光标和选区怎么处理  HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?  如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?  美食网站链接制作教程视频,哪个教做美食的网站比较专业点?  Win10如何卸载预装Edge扩展_Win10卸载Edge扩展教程【方法】  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  高端企业智能建站程序:SEO优化与响应式模板定制开发  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  在线制作视频的网站有哪些,电脑如何制作视频短片?  Claude怎样写约束型提示词_Claude约束提示词写法【教程】  如何快速打造个性化非模板自助建站?  头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?  详解vue.js组件化开发实践  Laravel如何实现事件和监听器?(Event & Listener实战)  开心动漫网站制作软件下载,十分开心动画为何停播?  javascript日期怎么处理_如何格式化输出  网站制作企业,网站的banner和导航栏是指什么?  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  如何用ChatGPT准备面试 模拟面试问答与职场话术练习教程  如何有效防御Web建站篡改攻击?  Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  Laravel Blade组件怎么用_Laravel可复用视图组件的创建与使用  Laravel如何集成微信支付SDK_Laravel使用yansongda-pay实现扫码支付【实战】  JavaScript模板引擎Template.js使用详解