Fixed thread safety for RCTImageLoader

This commit is contained in:
Nick Lockwood 2015-05-11 13:08:39 -07:00 committed by Christopher Chedeau
parent 6b2c88feec
commit cfeae15c1f
2 changed files with 41 additions and 21 deletions

View File

@ -15,6 +15,11 @@
@interface RCTImageLoader : NSObject
+ (ALAssetsLibrary *)assetsLibrary;
/**
* Can be called from any thread.
* Will always call callback on main thread.
*/
+ (void)loadImageWithTag:(NSString *)tag
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;

View File

@ -33,13 +33,24 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
return queue;
}
NSError *errorWithMessage(NSString *message)
static NSError *RCTErrorWithMessage(NSString *message)
{
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
return error;
}
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
{
if ([NSThread isMainThread]) {
callback(error, image);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
callback(error, image);
});
}
}
@implementation RCTImageLoader
+ (ALAssetsLibrary *)assetsLibrary
@ -52,6 +63,10 @@ NSError *errorWithMessage(NSString *message)
return assetsLibrary;
}
/**
* Can be called from any thread.
* Will always call callback on main thread.
*/
+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id image))callback
{
if ([imageTag hasPrefix:@"assets-library"]) {
@ -68,18 +83,18 @@ NSError *errorWithMessage(NSString *message)
ALAssetRepresentation *representation = [asset defaultRepresentation];
ALAssetOrientation orientation = [representation orientation];
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation];
callback(nil, image);
RCTDispatchCallbackOnMainQueue(callback, nil, image);
}
});
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
NSError *error = errorWithMessage(errorText);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
}
} failureBlock:^(NSError *loadError) {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
NSError *error = errorWithMessage(errorText);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
}];
} else if ([imageTag hasPrefix:@"ph://"]) {
// Using PhotoKit for iOS 8+
@ -90,19 +105,19 @@ NSError *errorWithMessage(NSString *message)
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) {
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
NSError *error = errorWithMessage(errorText);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
return;
}
PHAsset *asset = [results firstObject];
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
callback(nil, result);
RCTDispatchCallbackOnMainQueue(callback, nil, result);
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
NSError *error = errorWithMessage(errorText);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
return;
}
}];
@ -110,33 +125,33 @@ NSError *errorWithMessage(NSString *message)
NSURL *url = [NSURL URLWithString:imageTag];
if (!url) {
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
callback(errorWithMessage(errorMessage), nil);
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
return;
}
[[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) {
if (error) {
callback(error, nil);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
} else {
callback(nil, [UIImage imageWithData:data]);
RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]);
}
}];
} else if ([[imageTag pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
} else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
if (image) {
callback(nil, image);
RCTDispatchCallbackOnMainQueue(callback, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
NSError *error = errorWithMessage(errorMessage);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
}
} else {
UIImage *image = [RCTConvert UIImage:imageTag];
if (image) {
callback(nil, image);
RCTDispatchCallbackOnMainQueue(callback, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
NSError *error = errorWithMessage(errorMessage);
callback(error, nil);
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(callback, error, nil);
}
}
}