本文承接ReactNative图片下载过程(一),继续深入研究。源码在文末,如想直接看可翻至最后。
整个图片的下载过程,基本思想就是先在模块里找合适的loader去下载,如果找不到则用RCTNetwork去下,当然我们看源码肯定不能只懂思想,一些细节处理也是很值得关注的,比如缓存处理,缓存的访问限制,取消下载的处理,下载完回调函数的线程处理等等。
1、处理图片下载完成后的回调函数,将回调函数放在非主线程中处理,防止耗费资源,不得不说其对于输入的判断都很到位
RCTImageLoaderCompletionBlockcompletionHandler = ^(NSError*error, UIImage*image) {
if([NSThreadisMainThread]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (!cancelled) {
completionBlock(error, image);
}
});
} elseif(!cancelled) {
completionBlock(error, image);
}
};
2、判断图片的URL是否为空,如果为空则抛出错误。
if(imageTag.length== 0) {
completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil);
return^{};
}
3、建NSURLCache串行队列
// All accessto URL cache must be serialized
if(!_URLCacheQueue) {
_URLCacheQueue= dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
}
4、异步执行队列_URLCacheQueue,初始化URLCache缓存
if(!_URLCache) {
_URLCache= [[NSURLCachealloc] initWithMemoryCapacity:5* 1024* 1024// 5MB
diskCapacity:200* 1024* 1024// 200MB
diskPath:@"React/RCTImageDownloader"];
}
5、找到可以执行下载操作的loader,如果找到则执行下载操作loadImageForURL:
RCTImageLoader*strongSelf = weakSelf;
if(cancelled || !strongSelf) {
return;
}
// Findsuitable image URL loader
NSURLRequest*request = [RCTConvertNSURLRequest:imageTag];
id loadHandler = [strongSelf imageURLLoaderForURL:request.URL];
if(loadHandler) {
cancelLoad = [loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
completionHandler:completionHandler]?: ^{};
return;
}
6、调试用,检查网络模块是否可用并能下载图片
// Checkif networking module is available
if(RCT_DEBUG&& ![_bridgerespondsToSelector:@selector(networking)]) {
RCTLogError(@"No suitableimage URL loader found for %@. You may need to "
" import the RCTNetworkinglibrary in order to load images.",
imageTag);
return;
}
// Checkif networking module can load image
if(RCT_DEBUG&& ![_bridge.networkingcanHandleRequest:request]) {
RCTLogError(@"No suitableimage URL loader found for %@",imageTag);
return;
}
7、使用网络模块来下载图片
__blockRCTImageLoaderCancellationBlockcancelDecode = nil;
RCTURLRequestCompletionBlockprocessResponse =
^(NSURLResponse*response, NSData*data, NSError*error) {
// 检查是否有下载出错或没有数据返回
if(error) {
completionHandler(error, nil);
return;
} elseif(!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
return;
}
// 检查HTTP请求是否返回错误,如有误则抛出返回的状态码
if([response isKindOfClass:[NSHTTPURLResponseclass]]) {
NSInteger statusCode =((NSHTTPURLResponse*)response).statusCode;
if (statusCode != 200) {
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode
userInfo:nil], nil);
return;
}
}
// 图片解码
cancelDecode = [strongSelf decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionHandler];
};
8、添加png后缀
//判断请求的url是否为fileURL并且是否没有后缀,如果都是则添加后缀png
if(request.URL.fileURL&& request.URL.pathExtension.length== 0) {
NSMutableURLRequest*mutableRequest = [request mutableCopy];
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
request = mutableRequest;
}
9、根据request在responseCache缓存中查找是否已经有了缓存,如果有则执行缓存内容
NSCachedURLResponse*cachedResponse = [_URLCachecachedResponseForRequest:request];
if(cachedResponse) {
processResponse(cachedResponse.response,cachedResponse.data, nil);
return;
}
10、使用RCTNetworkTask来下载图片
//调用network模块来发起请求
RCTNetworkTask*task = [_bridge.networkingnetworkTaskWithRequest:request completionBlock:
^(NSURLResponse*response, NSData*data, NSError*error) {
if(error) {
completionHandler(error, nil);
return;
}
dispatch_async(_URLCacheQueue, ^{
// 将请求的回应缓存起来
BOOL isHTTPRequest =[request.URL.scheme hasPrefix:@"http"];
[strongSelf->_URLCache storeCachedResponse:
[[NSCachedURLResponse alloc] initWithResponse:response
data:data
userInfo:nil
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
forRequest:request];
// 处理返回的数据
processResponse(response,data, nil);
});
}];
11、开始下载
task.downloadProgressBlock= progressHandler;
[task start];
cancelLoad = ^{
[task cancel];
if(cancelDecode) {
cancelDecode();
}
};
12、返回一个可取消下载的block
return^{
if(cancelLoad) {
cancelLoad();
}
//执行1和cancelled的或运算然后把结果存入&cancelled
OSAtomicOr32Barrier(1, &cancelled);
};
官方部分源码
RCTImageLoader.m
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString*)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
__blockvolatileuint32_tcancelled = 0;
__blockvoid(^cancelLoad)(void) = nil;
__weakRCTImageLoader*weakSelf = self;
RCTImageLoaderCompletionBlockcompletionHandler = ^(NSError*error, UIImage*image) {
if([NSThreadisMainThread]) {
//Most loaders do not return on the main thread, so caller is probably not
//expecting it, and may do expensive post-processing in the callback
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (!cancelled) {
completionBlock(error, image);
}
});
} elseif(!cancelled) {
completionBlock(error, image);
}
};
if(imageTag.length== 0) {
completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil);
return^{};
}
// All accessto URL cache must be serialized
if(!_URLCacheQueue) {
_URLCacheQueue= dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
}
dispatch_async(_URLCacheQueue, ^{
if(!_URLCache) {
_URLCache= [[NSURLCachealloc] initWithMemoryCapacity:5* 1024* 1024// 5MB
diskCapacity:200* 1024* 1024// 200MB
diskPath:@"React/RCTImageDownloader"];
}
RCTImageLoader*strongSelf = weakSelf;
if(cancelled || !strongSelf) {
return;
}
// Findsuitable image URL loader
NSURLRequest*request = [RCTConvertNSURLRequest:imageTag];
id loadHandler = [strongSelf imageURLLoaderForURL:request.URL];
if(loadHandler) {
cancelLoad = [loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
completionHandler:completionHandler]?: ^{};
return;
}
// Checkif networking module is available
if(RCT_DEBUG&& ![_bridgerespondsToSelector:@selector(networking)]) {
RCTLogError(@"No suitableimage URL loader found for %@. You may need to "
" import the RCTNetworkinglibrary in order to load images.",
imageTag);
return;
}
// Checkif networking module can load image
if(RCT_DEBUG&& ![_bridge.networkingcanHandleRequest:request]) {
RCTLogError(@"No suitableimage URL loader found for %@",imageTag);
return;
}
// Usenetworking module to load image
__blockRCTImageLoaderCancellationBlockcancelDecode = nil;
RCTURLRequestCompletionBlockprocessResponse =
^(NSURLResponse*response, NSData*data, NSError*error) {
//Check for system errors
if(error) {
completionHandler(error, nil);
return;
} elseif(!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
return;
}
//Check for http errors
if([response isKindOfClass:[NSHTTPURLResponseclass]]) {
NSInteger statusCode =((NSHTTPURLResponse*)response).statusCode;
if (statusCode != 200) {
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode
userInfo:nil], nil);
return;
}
}
//Decode image
cancelDecode = [strongSelf decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionHandler];
};
// Addmissing png extension
if(request.URL.fileURL&& request.URL.pathExtension.length== 0) {
NSMutableURLRequest*mutableRequest = [request mutableCopy];
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
request = mutableRequest;
}
// Checkfor cached response before reloading
// TODO:move URL cache out of RCTImageLoader into its own module
NSCachedURLResponse*cachedResponse = [_URLCachecachedResponseForRequest:request];
if(cachedResponse) {
processResponse(cachedResponse.response,cachedResponse.data, nil);
return;
}
//Download image
RCTNetworkTask*task = [_bridge.networkingnetworkTaskWithRequest:request completionBlock:
^(NSURLResponse*response, NSData*data, NSError*error) {
if(error) {
completionHandler(error, nil);
return;
}
dispatch_async(_URLCacheQueue, ^{
// Cache the response
// TODO: move URL cache out of RCTImageLoader into itsown module
BOOL isHTTPRequest =[request.URL.scheme hasPrefix:@"http"];
[strongSelf->_URLCache storeCachedResponse:
[[NSCachedURLResponse alloc] initWithResponse:response
data:data
userInfo:nil
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
forRequest:request];
// Process image data
processResponse(response,data, nil);
});
}];
task.downloadProgressBlock= progressHandler;
[task start];
cancelLoad = ^{
[task cancel];
if(cancelDecode) {
cancelDecode();
}
};
});
return^{
if(cancelLoad) {
cancelLoad();
}
OSAtomicOr32Barrier(1, &cancelled);
};
}