本文為xcode模擬器測試,rn版本0.44.3
突然想學(xué)習(xí)下RN是如何封裝ios中的UIImage的,看著看著發(fā)現(xiàn)圖片的緩存問題是個坑。。
先看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必須設(shè)置圖片寬高,3不需設(shè)置。
對應(yīng)的ios原生端文件是RCTImageViewManager,暴露的屬性
RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);
就是js中Image組件的屬性source,在js中設(shè)置source會觸發(fā)該屬性的setter方法。進入RCTImageView的
- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources { if (![imageSources isEqual:_imageSources]) { _imageSources = [imageSources copy]; [self reloadImage]; } }
通過此方法中斷點打印imageSources,依次得到下面結(jié)果:
可見,Image組件加載圖片都是采用URL的形式,將圖片當(dāng)作網(wǎng)絡(luò)資源。不同的是URL的類型:
加載網(wǎng)絡(luò)上圖片 : 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); }; // 下載圖片完成后回調(diào) __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是圖片類型,則直接回調(diào) // 此處,如果是第二種情況,則會滿足,其他情況繼續(xù)走下面方法 if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { cancelLoad = nil; completionBlock(error, imageOrData); return; } // 在內(nèi)存中查看是否存在該url對應(yīng)的字節(jié)碼圖片 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種情況用網(wǎng)絡(luò)請求下載,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是網(wǎng)絡(luò)請求時返回來的
代碼如下:
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
1、3方式每次加載都是一個網(wǎng)絡(luò)請求,那么網(wǎng)絡(luò)請求的時間總是變化的,于是responseDate是變化的,cacheKey不唯一,所以雖然系統(tǒng)做了圖片的緩存,但是每次取出的都為nil,緩存無效。
2方式加載具體方法在RCTLocalAssetImageLoader.m中,其調(diào)用的是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自帶的圖片,這個是有內(nèi)存緩存的。
綜上,對react-native圖片加載
1、3情況,沒有內(nèi)存緩存
2情況有系統(tǒng)默認(rèn)的內(nèi)存緩存
所有情況都沒有磁盤緩存
想讓內(nèi)存緩存生效,只需要改變cacheKey的生成規(guī)則即可。
補充:沙盒下面的Library/Caches/項目bunderId號/fsCachedData文件夾里面會磁盤緩存大于一定值(測試約為5kb)的圖片和文件,這個是NSURLSession網(wǎng)絡(luò)請求系統(tǒng)默認(rèn)的緩存類NSURLCache自動生成的,非圖片的磁盤緩存。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com