Fixed threading issues in RCTImageDownloader
This commit is contained in:
Родитель
5865cfa956
Коммит
15eb5fde51
|
@ -16,14 +16,30 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
|
||||||
|
|
||||||
+ (instancetype)sharedInstance;
|
+ (instancetype)sharedInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a block of raw data and returns it. Note that the callback block
|
||||||
|
* will not be executed on the same thread you called the method from, nor on
|
||||||
|
* the main thread. Returns a token that can be used to cancel the download.
|
||||||
|
*/
|
||||||
- (id)downloadDataForURL:(NSURL *)url
|
- (id)downloadDataForURL:(NSURL *)url
|
||||||
block:(RCTDataDownloadBlock)block;
|
block:(RCTDataDownloadBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads an image and decompresses it a the size specified. The compressed
|
||||||
|
* image will be cached in memory and to disk. Note that the callback block
|
||||||
|
* will not be executed on the same thread you called the method from, nor on
|
||||||
|
* the main thread. Returns a token that can be used to cancel the download.
|
||||||
|
*/
|
||||||
- (id)downloadImageForURL:(NSURL *)url
|
- (id)downloadImageForURL:(NSURL *)url
|
||||||
size:(CGSize)size
|
size:(CGSize)size
|
||||||
scale:(CGFloat)scale
|
scale:(CGFloat)scale
|
||||||
block:(RCTImageDownloadBlock)block;
|
block:(RCTImageDownloadBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel an in-flight download. If multiple requets have been made for the
|
||||||
|
* same image, only the request that relates to the token passed will be
|
||||||
|
* cancelled.
|
||||||
|
*/
|
||||||
- (void)cancelDownload:(id)downloadToken;
|
- (void)cancelDownload:(id)downloadToken;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -17,12 +17,12 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
|
||||||
@implementation RCTImageDownloader
|
@implementation RCTImageDownloader
|
||||||
{
|
{
|
||||||
RCTCache *_cache;
|
RCTCache *_cache;
|
||||||
|
dispatch_queue_t _processingQueue;
|
||||||
NSMutableDictionary *_pendingBlocks;
|
NSMutableDictionary *_pendingBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)sharedInstance
|
+ (instancetype)sharedInstance
|
||||||
{
|
{
|
||||||
RCTAssertMainThread();
|
|
||||||
static RCTImageDownloader *sharedInstance;
|
static RCTImageDownloader *sharedInstance;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
|
@ -35,27 +35,32 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
|
||||||
{
|
{
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"];
|
_cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"];
|
||||||
_pendingBlocks = [NSMutableDictionary dictionary];
|
_processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
_pendingBlocks = [[NSMutableDictionary alloc] init];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)cacheKeyForURL:(NSURL *)url
|
static NSString *RCTCacheKeyForURL(NSURL *url)
|
||||||
{
|
{
|
||||||
return url.absoluteString;
|
return url.absoluteString;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
|
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
|
||||||
{
|
{
|
||||||
NSString *cacheKey = [self cacheKeyForURL:url];
|
NSString *cacheKey = RCTCacheKeyForURL(url);
|
||||||
|
|
||||||
__block BOOL cancelled = NO;
|
__block BOOL cancelled = NO;
|
||||||
__block NSURLSessionDataTask *task = nil;
|
__block NSURLSessionDataTask *task = nil;
|
||||||
|
|
||||||
dispatch_block_t cancel = ^{
|
dispatch_block_t cancel = ^{
|
||||||
|
|
||||||
cancelled = YES;
|
cancelled = YES;
|
||||||
|
|
||||||
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
|
dispatch_async(_processingQueue, ^{
|
||||||
[pendingBlocks removeObject:block];
|
NSMutableArray *pendingBlocks = self->_pendingBlocks[cacheKey];
|
||||||
|
[pendingBlocks removeObject:block];
|
||||||
|
});
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
[task cancel];
|
[task cancel];
|
||||||
|
@ -63,56 +68,60 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
|
dispatch_async(_processingQueue, ^{
|
||||||
if (pendingBlocks) {
|
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
|
||||||
[pendingBlocks addObject:block];
|
if (pendingBlocks) {
|
||||||
} else {
|
[pendingBlocks addObject:block];
|
||||||
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
|
|
||||||
|
|
||||||
__weak RCTImageDownloader *weakSelf = self;
|
|
||||||
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
|
||||||
RCTImageDownloader *strongSelf = weakSelf;
|
|
||||||
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
|
||||||
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
|
||||||
|
|
||||||
for (RCTCachedDataDownloadBlock block in blocks) {
|
|
||||||
block(cached, data, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if ([_cache hasDataForKey:cacheKey]) {
|
|
||||||
[_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) {
|
|
||||||
if (!cancelled) runBlocks(YES, data, nil);
|
|
||||||
}];
|
|
||||||
} else {
|
} else {
|
||||||
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
|
||||||
if (!cancelled) runBlocks(NO, data, error);
|
|
||||||
}];
|
|
||||||
|
|
||||||
[task resume];
|
__weak RCTImageDownloader *weakSelf = self;
|
||||||
|
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
||||||
|
|
||||||
|
RCTImageDownloader *strongSelf = weakSelf;
|
||||||
|
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
||||||
|
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
||||||
|
for (RCTCachedDataDownloadBlock block in blocks) {
|
||||||
|
block(cached, data, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ([_cache hasDataForKey:cacheKey]) {
|
||||||
|
[_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) {
|
||||||
|
if (!cancelled) {
|
||||||
|
runBlocks(YES, data, nil);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
if (!cancelled) {
|
||||||
|
runBlocks(NO, data, error);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return [cancel copy];
|
return [cancel copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
|
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
|
||||||
{
|
{
|
||||||
NSString *cacheKey = [self cacheKeyForURL:url];
|
NSString *cacheKey = RCTCacheKeyForURL(url);
|
||||||
__weak RCTImageDownloader *weakSelf = self;
|
__weak RCTImageDownloader *weakSelf = self;
|
||||||
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
|
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
RCTImageDownloader *strongSelf = weakSelf;
|
RCTImageDownloader *strongSelf = weakSelf;
|
||||||
[strongSelf->_cache setData:data forKey:cacheKey];
|
[strongSelf->_cache setData:data forKey:cacheKey];
|
||||||
}
|
}
|
||||||
|
block(data, error);
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
block(data, error);
|
|
||||||
});
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
|
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size
|
||||||
|
scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
|
||||||
{
|
{
|
||||||
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
|
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
|
||||||
|
|
||||||
|
@ -142,20 +151,14 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
|
||||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
UIGraphicsEndImageContext();
|
UIGraphicsEndImageContext();
|
||||||
}
|
}
|
||||||
|
block(image, nil);
|
||||||
// TODO: should we cache the decompressed image?
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
block(image, nil);
|
|
||||||
});
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)cancelDownload:(id)downloadToken
|
- (void)cancelDownload:(id)downloadToken
|
||||||
{
|
{
|
||||||
if (downloadToken) {
|
if (downloadToken) {
|
||||||
dispatch_block_t block = (id)downloadToken;
|
((dispatch_block_t)downloadToken)();
|
||||||
block();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
#import "RCTImageDownloader.h"
|
#import "RCTImageDownloader.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
|
||||||
NSError *errorWithMessage(NSString *message) {
|
NSError *errorWithMessage(NSString *message)
|
||||||
|
{
|
||||||
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
|
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
|
||||||
NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
|
NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
|
||||||
return error;
|
return error;
|
||||||
|
|
|
@ -61,20 +61,24 @@
|
||||||
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
|
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
|
||||||
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
|
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
|
||||||
if (data) {
|
if (data) {
|
||||||
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
self.layer.contentsScale = 1.0;
|
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
|
||||||
self.layer.minificationFilter = kCAFilterLinear;
|
self.layer.contentsScale = 1.0;
|
||||||
self.layer.magnificationFilter = kCAFilterLinear;
|
self.layer.minificationFilter = kCAFilterLinear;
|
||||||
[self.layer addAnimation:animation forKey:@"contents"];
|
self.layer.magnificationFilter = kCAFilterLinear;
|
||||||
|
[self.layer addAnimation:animation forKey:@"contents"];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// TODO: handle errors
|
// TODO: handle errors
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
|
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
|
||||||
if (image) {
|
if (image) {
|
||||||
[self.layer removeAnimationForKey:@"contents"];
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
self.layer.contentsScale = image.scale;
|
[self.layer removeAnimationForKey:@"contents"];
|
||||||
self.layer.contents = (__bridge id)image.CGImage;
|
self.layer.contentsScale = image.scale;
|
||||||
|
self.layer.contents = (__bridge id)image.CGImage;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// TODO: handle errors
|
// TODO: handle errors
|
||||||
}];
|
}];
|
||||||
|
|
Загрузка…
Ссылка в новой задаче