本文為xcode模擬器測(cè)試,rn版本0.44.3
突然想學(xué)習(xí)下RN是如何封裝ios中的UIImage的,看著看著發(fā)現(xiàn)圖片的緩存問(wèn)題是個(gè)坑绢片。。岛琼。
先看js端圖片使用的三種方式底循,依次排序1、2槐瑞、3
<Image source={{uri:url}} style={{width:200,height:200}}/> 1熙涤、 加載遠(yuǎn)程圖片
<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è)置悼沿。
對(duì)應(yīng)的ios原生端文件是RCTImageViewManager等舔,暴露的屬性
RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray<RCTImageSource *>);
就是js中Image組件的屬性source,在js中設(shè)置source會(huì)觸發(fā)該屬性的setter方法糟趾。進(jìn)入RCTImageView的
- (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
{
if (![imageSources isEqual:_imageSources]) {
_imageSources = [imageSources copy];
[self reloadImage];
}
}
通過(guò)此方法中斷點(diǎn)打印imageSources慌植,依次得到下面結(jié)果:
可見(jiàn),Image組件加載圖片都是采用URL的形式义郑,將圖片當(dāng)作網(wǎng)絡(luò)資源蝶柿。不同的是URL的類(lèi)型:
加載網(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是圖片類(lèi)型非驮,則直接回調(diào)
// 此處交汤,如果是第二種情況,則會(huì)滿(mǎn)足院尔,其他情況繼續(xù)走下面方法
if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
cancelLoad = nil;
completionBlock(error, imageOrData);
return;
}
// 在內(nèi)存中查看是否存在該url對(duì)應(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;
}
}
// 若沒(méi)有緩存蜻展,則將圖片解壓喉誊,再將解壓后圖片緩存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);
};
// 具體的解壓過(guò)程
cancelLoad = [strongSelf decodeImageData:imageOrData
size:size
scale:scale
clipped:clipped
resizeMode:resizeMode
completionBlock:decodeCompletionHandler];
};
// 走具體的方法加載圖片,1纵顾、3種情況用網(wǎng)絡(luò)請(qǐng)求下載伍茄,2情況加載本地文件
cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest
size:size
scale:scale
resizeMode:resizeMode
progressBlock:progressBlock
partialLoadBlock:partialLoadBlock
completionBlock:completionHandler];
return cancellationBlock;
}
具體的緩存類(lèi)是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是個(gè)常量敷矫,為1048576,也就是只緩存小于1MB的圖片汉额。
問(wèn)題出在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ò)請(qǐng)求時(shí)返回來(lái)的
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
1蠕搜、3方式每次加載都是一個(gè)網(wǎng)絡(luò)請(qǐng)求怎茫,那么網(wǎng)絡(luò)請(qǐng)求的時(shí)間總是變化的,于是responseDate是變化的妓灌,cacheKey不唯一轨蛤,所以雖然系統(tǒng)做了圖片的緩存,但是每次取出的都為nil虫埂,緩存無(wú)效祥山。
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;
}
可見(jiàn)是采用[UIImage imageNamed:imageName]的方式加載xcode自帶的圖片掉伏,這個(gè)是有內(nèi)存緩存的缝呕。
綜上,對(duì)react-native圖片加載
1斧散、3情況供常,沒(méi)有內(nèi)存緩存
2情況有系統(tǒng)默認(rèn)的內(nèi)存緩存
所有情況都沒(méi)有磁盤(pán)緩存
想讓內(nèi)存緩存生效,只需要改變cacheKey的生成規(guī)則即可颅湘。
補(bǔ)充:沙盒下面的Library/Caches/項(xiàng)目bunderId號(hào)/fsCachedData文件夾里面會(huì)磁盤(pán)緩存大于一定值(測(cè)試約為5kb)的圖片和文件话侧,這個(gè)是NSURLSession網(wǎng)絡(luò)請(qǐng)求系統(tǒng)默認(rèn)的緩存類(lèi)NSURLCache自動(dòng)生成的,非圖片的磁盤(pán)緩存闯参。