源码的图形缓存难题,源码的图形缓存

2019-06-08 12:42 来源:未知

本文为xcode模拟器测试,rn版本0.44.3

本文为xcode模拟器测试,rn版本0.44.3
突然想学习下RN是如何封装ios中的UIImage的,看着看着发现图片的缓存问题是个坑。。。
先看js端图片使用的三种方式,依次排序1、2、3

先看js端图片使用的三种方式,依次排序1、2、3

突然想学习下RN是如何封装ios中的UIImage的,看着看着发现图片的缓存问题是个坑。。。

 <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中图片
<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中图片

先看js端图片使用的三种方式,依次排序1、2、3

1、2必须设置图片宽高,3不需设置。

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中图片

对应的ios原生端文件是RCTImageViewManager,暴露的属性

韦德娱乐1946网页版,对应的ios原生端文件是RCTImageViewManager,暴露的属性

1、2必须设置图片宽高,3不需设置。

    RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);
 RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);

对应的ios原生端文件是RCTImageViewManager,暴露的属性

就是js中Image组件的属性source,在js中设置source会触发该属性的setter方法。进入RCTImageView的

就是js中Image组件的属性source,在js中设置source会触发该属性的setter方法。进入RCTImageView的

RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);
- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
 {
         if (![imageSources isEqual:_imageSources]) {
               _imageSources = [imageSources copy];
               [self reloadImage];
         }
 }
- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
 {
         if (![imageSources isEqual:_imageSources]) {
               _imageSources = [imageSources copy];
               [self reloadImage];
         }
 }

就是js中Image组件的属性source,在js中设置source会触发该属性的setter方法。进入RCTImageView的

通过此方法中断点打印imageSources,依次得到下面结果:

通过此方法中断点打印imageSources,依次得到下面结果:

- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
 {
   if (![imageSources isEqual:_imageSources]) {
    _imageSources = [imageSources copy];
    [self reloadImage];
   }
 }

韦德娱乐1946网页版 1

 韦德娱乐1946网页版 2

通过此方法中断点打印imageSources,依次得到下面结果:

可见,Image组件加载图片都是采用URL的形式,将图片当作网络资源。不同的是URL的类型:

可见,Image组件加载图片都是采用URL的形式,将图片当作网络资源。不同的是URL的类型:

韦德娱乐1946网页版 3

   加载网络上图片         :  http://
   加载xcode资源         :  file://
   加载js中图片           :   http://localhost:8081
   加载网络上图片         :  http://
   加载xcode资源         :  file://
   加载js中图片           :   http://localhost:8081

可见,Image组件加载图片都是采用URL的形式,将图片当作网络资源。不同的是URL的类型:

追踪setter方法,到RCTImageLoader.m中的如下方法

追踪setter方法,到RCTImageLoader.m中的如下方法

 加载网络上图片   : http://
 加载xcode资源   : file://
 加载js中图片   : http://localhost:8081
- (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;
}
- (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;
}

追踪setter方法,到RCTImageLoader.m中的如下方法

具体的缓存类是RCTImageCache,采用NSCache缓存,方法

具体的缓存类是RCTImageCache,采用NSCache缓存,方法

- (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;
}
- (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];
  }
}
- (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];
  }
}

具体的缓存类是RCTImageCache,采用NSCache缓存,方法

RCTMaxCachableDecodedImageSizeInBytes是个常量,为1048576,也就是只缓存小于1MB的图片。
问题出在cacheKey,查看缓存key的方法

RCTMaxCachableDecodedImageSizeInBytes是个常量,为1048576,也就是只缓存小于1MB的图片。
问题出在cacheKey,查看缓存key的方法

- (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];
 }
}
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];
}
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];
}

RCTMaxCachableDecodedImageSizeInBytes是个常量,为1048576,也就是只缓存小于1MB的图片。

缓存key的生成方法中包含了responseDate,responseDate是网络请求时返回来的

缓存key的生成方法中包含了responseDate,responseDate是网络请求时返回来的

问题出在cacheKey,查看缓存key的方法

responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
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];
}

1、3方式每次加载都是一个网络请求,那么网络请求的时间总是变化的,于是responseDate是变化的,cacheKey不唯一,所以虽然系统做了图片的缓存,但是每次取出的都为nil,缓存无效。

1、3方式每次加载都是一个网络请求,那么网络请求的时间总是变化的,于是responseDate是变化的,cacheKey不唯一,所以虽然系统做了图片的缓存,但是每次取出的都为nil,缓存无效。

缓存key的生成方法中包含了responseDate,responseDate是网络请求时返回来的

2方式加载具体方法在RCTLocalAssetImageLoader.m中,其调用的是RCTUtils的RCTImageFromLocalAssetURL方法

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 *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
{
// .....省略各种处理
  UIImage *image = nil;
  if (bundle) {
    image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
  } else {
    image = [UIImage imageNamed:imageName];
  }
// .....省略各种处理
  return image;
}

responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];

可见是采用[UIImage imageNamed:imageName]的方式加载xcode自带的图片,这个是有内存缓存的。

可见是采用[UIImage imageNamed:imageName]的方式加载xcode自带的图片,这个是有内存缓存的。

1、3方式每次加载都是一个网络请求,那么网络请求的时间总是变化的,于是responseDate是变化的,cacheKey不唯一,所以虽然系统做了图片的缓存,但是每次取出的都为nil,缓存无效。

综上,对react-native图片加载
1、3情况,没有内存缓存
2情况有系统默认的内存缓存
所有情况都没有磁盘缓存

综上,对react-native图片加载
1、3情况,没有内存缓存
2情况有系统默认的内存缓存
所有情况都没有磁盘缓存

2方式加载具体方法在RCTLocalAssetImageLoader.m中,其调用的是RCTUtils的RCTImageFromLocalAssetURL方法

想让内存缓存生效,只需要改变cacheKey的生成规则即可。

想让内存缓存生效,只需要改变cacheKey的生成规则即可。

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;
}

补充:沙盒下面的Library/Caches/项目bunderId号/fsCachedData文件夹里面会磁盘缓存大于一定值(测试约为5kb)的图片和文件,这个是NSURLSession网络请求系统默认的缓存类NSURLCache自动生成的,非图片的磁盘缓存。

补充:沙盒下面的Library/Caches/项目bunderId号/fsCachedData文件夹里面会磁盘缓存大于一定值(测试约为5kb)的图片和文件,这个是NSURLSession网络请求系统默认的缓存类NSURLCache自动生成的,非图片的磁盘缓存。

可见是采用[UIImage imageNamed:imageName]的方式加载xcode自带的图片,这个是有内存缓存的。

综上,对react-native图片加载

1、3情况,没有内存缓存

2情况有系统默认的内存缓存

所有情况都没有磁盘缓存

想让内存缓存生效,只需要改变cacheKey的生成规则即可。

补充:沙盒下面的Library/Caches/项目bunderId号/fsCachedData文件夹里面会磁盘缓存大于一定值(测试约为5kb)的图片和文件,这个是NSURLSession网络请求系统默认的缓存类NSURLCache自动生成的,非图片的磁盘缓存。

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

TAG标签: 韦德娱乐1946
版权声明:本文由韦德娱乐1946_韦德娱乐1946网页版|韦德国际1946官网发布于韦德娱乐1946网页版,转载请注明出处:源码的图形缓存难题,源码的图形缓存