先看js端圖片使用的三種方式,依次排序一、二、3react
<Image source={{uri:url}} style={{width:200,height:200}}/> 一、 加載遠程圖片 <Image source={{uri:'1.png'}} style={{width:50,height:50}}/> 二、加載xcode中圖片 <Image source={require('../../../Resources/Images/Contact/conact_searchIcon@3x.png')}/> 三、加載js中圖片
一、2必須設置圖片寬高,3不需設置。ios
對應的ios原生端文件是RCTImageViewManager,暴露的屬性react-native
RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);
就是js中Image組件的屬性source,在js中設置source會觸發該屬性的setter方法。進入RCTImageView的xcode
- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources { if (![imageSources isEqual:_imageSources]) { _imageSources = [imageSources copy]; [self reloadImage]; } }
經過此方法中斷點打印imageSources,依次獲得下面結果:緩存
加載網絡上圖片 : 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]; }; // 走具體的方法加載圖片,一、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的方法fetch
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是網絡請求時返回來的ui
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
一、3方式每次加載都是一個網絡請求,那麼網絡請求的時間老是變化的,因而responseDate是變化的,cacheKey不惟一,因此雖然系統作了圖片的緩存,可是每次取出的都爲nil,緩存無效。url
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圖片加載
一、3狀況,沒有內存緩存
2狀況有系統默認的內存緩存
全部狀況都沒有磁盤緩存
想讓內存緩存生效,只須要改變cacheKey的生成規則便可。
補充:沙盒下面的Library/Caches/項目bunderId號/fsCachedData文件夾裏面會磁盤緩存大於必定值(測試約爲5kb)的圖片和文件,這個是NSURLSession網絡請求系統默認的緩存類NSURLCache自動生成的,非圖片的磁盤緩存。