Updates from Wed Feb 18

- [reactnative] s/SpinnerIOS/ActivityIndicatorIOS/ | Dan Witte
- [react-packager] Add a non-persistent mode for static builds | Amjad Masad
- [React Native] Fix stored file rejection when initializing cache | Alex Akers
- [React Native] Consolidate network requests in image downloader | Alex Akers
- [React Native] Update RCTCache | Alex Akers
- Converted all low-hanging RKBridgeModules in FBReactKit to RCTBridgeModules | Nick Lockwood
This commit is contained in:
Spencer Ahrens
2015-02-18 17:51:14 -08:00
parent 89db1a64aa
commit c88a1cd9b8
22 changed files with 430 additions and 272 deletions

View File

@@ -235,8 +235,9 @@ static NSDictionary *RCTRemoteModulesConfig()
RCTRemoteModulesByID = [[NSMutableDictionary alloc] init];
remoteModules = [[NSMutableDictionary alloc] init];
[RCTExportedMethodsByModule() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, NSArray *rawMethods, BOOL *stop) {
[RCTBridgeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName];
NSMutableDictionary *methods = [NSMutableDictionary dictionaryWithCapacity:rawMethods.count];
[rawMethods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) {
methods[method.JSMethodName] = @{
@@ -249,13 +250,15 @@ static NSDictionary *RCTRemoteModulesConfig()
@"moduleID": @(remoteModules.count),
@"methods": methods
};
Class cls = RCTBridgeModuleClasses()[moduleName];
if (RCTClassOverridesClassMethod(cls, @selector(constantsToExport))) {
module = [module mutableCopy];
((NSMutableDictionary *)module)[@"constants"] = [cls constantsToExport];
if (RCTClassOverridesClassMethod(moduleClass, @selector(constantsToExport))) {
NSDictionary *constants = [moduleClass constantsToExport];
if (constants.count) {
module = [module mutableCopy];
((NSMutableDictionary *)module)[@"constants"] = constants;
}
}
remoteModules[moduleName] = module;
remoteModules[moduleName] = [module copy];
// Add module lookup
RCTRemoteModulesByID[module[@"moduleID"]] = moduleName;
@@ -303,13 +306,13 @@ static NSDictionary *RCTLocalModulesConfig()
// Add globally used methods
[JSMethods addObjectsFromArray:@[
@"Bundler.runApplication",
@"RCTDeviceEventEmitter.emit",
@"RCTEventEmitter.receiveEvent",
@"RCTEventEmitter.receiveTouches",
]];
// NOTE: these methods are currently unused in the OSS project
// @"Dimensions.set",
// @"RCTDeviceEventEmitter.emit",
// @"RCTNativeAppEventEmitter.emit",
// @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",
@@ -376,10 +379,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_moduleInstances = [[NSMutableDictionary alloc] init];
[RCTBridgeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
if (_moduleInstances[moduleName] == nil) {
id<RCTBridgeModule> moduleInstance;
if ([moduleClass instancesRespondToSelector:@selector(initWithBridge:)]) {
_moduleInstances[moduleName] = [[moduleClass alloc] initWithBridge:self];
moduleInstance = [[moduleClass alloc] initWithBridge:self];
} else {
_moduleInstances[moduleName] = [[moduleClass alloc] init];
moduleInstance = [[moduleClass alloc] init];
}
if (moduleInstance) {
// If nil, the module doesn't support auto-instantiation
_moduleInstances[moduleName] = moduleInstance;
}
}
}];
@@ -522,7 +530,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
if (![buffer isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
return;
}
@@ -530,14 +538,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSUInteger bufferRowCount = [requestsArray count];
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
if (bufferRowCount != expectedFieldsCount) {
RCTLogMustFix(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
return;
}
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
id field = [requestsArray objectAtIndex:fieldIndex];
if (![field isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
RCTLogError(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
return;
}
}
@@ -549,7 +557,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSUInteger numRequests = [moduleIDs count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
if (!allSame) {
RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests);
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
@@ -578,30 +586,30 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
params:(NSArray *)params
{
if (![params isKindOfClass:[NSArray class]]) {
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
}
NSString *moduleName = RCTRemoteModulesByID[moduleID];
if (!moduleName) {
RCTLogMustFix(@"Unknown moduleID: %@", moduleID);
RCTLogError(@"Unknown moduleID: %@", moduleID);
return NO;
}
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
if (methodID >= methods.count) {
RCTLogMustFix(@"Unknown methodID: %zd for module: %@", methodID, moduleName);
RCTLogError(@"Unknown methodID: %zd for module: %@", methodID, moduleName);
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogMustFix(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName);
RCTLogError(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
moduleName,
method.JSMethodName);
return NO;
}
@@ -663,13 +671,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// TODO: it seems like an error if the param doesn't respond
// so we should probably surface that error rather than failing silently
#define CASE(_value, _type, _selector) \
case _value: \
if ([param respondsToSelector:@selector(_selector)]) { \
_type value = [param _selector]; \
[invocation setArgument:&value atIndex:argIdx]; \
shouldSet = NO; \
} \
break;
case _value: \
if ([param respondsToSelector:@selector(_selector)]) { \
_type value = [param _selector]; \
[invocation setArgument:&value atIndex:argIdx]; \
shouldSet = NO; \
} \
break;
CASE('c', char, charValue)
CASE('C', unsigned char, unsignedCharValue)
@@ -698,7 +706,7 @@ break;
[invocation invoke];
}
@catch (NSException *exception) {
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
}
});

View File

@@ -5,21 +5,90 @@
#import <UIKit/UIKit.h>
#import <sys/xattr.h>
static NSString *const CacheSubdirectoryName = @"ReactKit";
static NSString *const KeyExtendedAttributeName = @"com.facebook.ReactKit.RCTCacheManager.Key";
static dispatch_queue_t Queue;
static NSString *const RCTCacheSubdirectoryName = @"ReactKit";
static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.ReactKit.RCTCacheManager.Key";
static NSMapTable *RCTLivingCachesByName;
static NSError *RCTPOSIXError(int errorNumber)
{
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @(strerror(errorNumber))
};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo];
}
static NSString *RCTGetExtendedAttribute(NSURL *fileURL, NSString *key, NSError **error)
{
const char *path = fileURL.fileSystemRepresentation;
ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0);
if (length <= 0) {
if (error) *error = RCTPOSIXError(errno);
return nil;
}
char *buffer = malloc(length);
length = getxattr(path, key.UTF8String, buffer, length, 0, 0);
if (length > 0) {
return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
free(buffer);
if (error) *error = RCTPOSIXError(errno);
return nil;
}
static BOOL RCTSetExtendedAttribute(NSURL *fileURL, NSString *key, NSString *value, NSError **error)
{
const char *path = fileURL.fileSystemRepresentation;
int result;
if (value) {
const char *valueUTF8String = value.UTF8String;
result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0);
} else {
result = removexattr(path, key.UTF8String, 0);
}
if (result) {
if (error) *error = RCTPOSIXError(errno);
return NO;
}
return YES;
}
#pragma mark - Cache Record -
@interface RCTCacheRecord : NSObject
@property (nonatomic, copy) NSUUID *UUID;
@property (readonly) NSUUID *UUID;
@property (readonly, weak) dispatch_queue_t queue;
@property (nonatomic, copy) NSData *data;
@end
@implementation RCTCacheRecord
- (instancetype)initWithUUID:(NSUUID *)UUID
{
if ((self = [super init])) {
_UUID = [UUID copy];
}
return self;
}
- (void)enqueueBlock:(dispatch_block_t)block
{
dispatch_queue_t queue = _queue;
if (!queue) {
NSString *queueName = [NSString stringWithFormat:@"com.facebook.ReactKit.RCTCache.%@", _UUID.UUIDString];
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
_queue = queue;
}
dispatch_async(queue, block);
}
@end
#pragma mark - Cache
@@ -35,9 +104,10 @@ static dispatch_queue_t Queue;
+ (void)initialize
{
if (self == [RCTCache class]) {
Queue = dispatch_queue_create("com.facebook.ReactKit.RCTCache", DISPATCH_QUEUE_SERIAL);
RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable];
}
}
- (instancetype)init
{
return [self initWithName:@"default"];
@@ -46,46 +116,39 @@ static dispatch_queue_t Queue;
- (instancetype)initWithName:(NSString *)name
{
NSParameterAssert(name.length < NAME_MAX);
RCTCache *cachedCache = [RCTLivingCachesByName objectForKey:name];
if (cachedCache) {
self = cachedCache;
return self;
}
if ((self = [super init])) {
_name = [name copy];
_fileManager = [[NSFileManager alloc] init];
_storage = [NSMutableDictionary dictionary];
NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:CacheSubdirectoryName isDirectory:YES];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:RCTCacheSubdirectoryName isDirectory:YES];
_cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES];
[_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL];
NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL];
for (NSURL *fileURL in fileURLs) {
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent];
if (!uuid) continue;
NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent];
if (!UUID) continue;
NSString *key = [self keyOfItemAtURL:fileURL error:NULL];
NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL);
if (!key) {
[_fileManager removeItemAtURL:fileURL error:NULL];
continue;
}
RCTCacheRecord *record = [[RCTCacheRecord alloc] init];
record.UUID = uuid;
_storage[key] = record;
_storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID];
}
}
return self;
}
- (void)runOnQueue:(dispatch_block_t)block
{
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
dispatch_async(Queue, ^{
if (block) block();
if (identifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:identifier];
}
});
}
- (BOOL)hasDataForKey:(NSString *)key
{
return _storage[key] != nil;
@@ -95,115 +158,67 @@ static dispatch_queue_t Queue;
{
NSParameterAssert(key.length > 0);
NSParameterAssert(completionHandler != nil);
[self runOnQueue:^{
RCTCacheRecord *record = _storage[key];
if (record && !record.data) {
RCTCacheRecord *record = _storage[key];
if (!record) {
completionHandler(nil);
return;
}
[record enqueueBlock:^{
if (!record.data) {
record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]];
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(record.data);
});
completionHandler(record.data);
}];
}
- (void)setData:(NSData *)data forKey:(NSString *)key
{
NSParameterAssert(key.length > 0);
[self runOnQueue:^{
RCTCacheRecord *record = _storage[key];
RCTCacheRecord *record = _storage[key];
if (!record) {
if (!data) return;
record = [[RCTCacheRecord alloc] initWithUUID:[NSUUID UUID]];
_storage[key] = record;
}
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
[record enqueueBlock:^{
if (data) {
if (!record) {
record = [[RCTCacheRecord alloc] init];
record.UUID = [NSUUID UUID];
_storage[key] = record;
}
record.data = data;
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
[data writeToURL:fileURL options:NSDataWritingAtomic error:NULL];
} else if (record) {
[_storage removeObjectForKey:key];
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL);
} else {
[_fileManager removeItemAtURL:fileURL error:NULL];
}
if (identifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:identifier];
}
}];
}
- (void)removeAllData
{
[self runOnQueue:^{
[_storage removeAllObjects];
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
dispatch_group_t group = dispatch_group_create();
NSDirectoryEnumerator *enumerator = [_fileManager enumeratorAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:nil];
for (NSURL *fileURL in enumerator) {
[_storage enumerateKeysAndObjectsUsingBlock:^(NSString *key, RCTCacheRecord *record, BOOL *stop) {
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
dispatch_group_async(group, record.queue, ^{
[_fileManager removeItemAtURL:fileURL error:NULL];
}
});
}];
}
#pragma mark - Extended Attributes
- (NSError *)errorWithPOSIXErrorNumber:(int)errorNumber
{
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @(strerror(errorNumber))
};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo];
}
- (BOOL)setAttribute:(NSString *)key value:(NSString *)value ofItemAtURL:(NSURL *)fileURL error:(NSError **)error
{
const char *path = fileURL.fileSystemRepresentation;
int result;
if (value) {
const char *valueUTF8String = value.UTF8String;
result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0);
} else {
result = removexattr(path, key.UTF8String, 0);
if (identifier != UIBackgroundTaskInvalid) {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:identifier];
});
}
if (result) {
if (error) *error = [self errorWithPOSIXErrorNumber:errno];
return NO;
}
return YES;
}
- (NSString *)attribute:(NSString *)key ofItemAtURL:(NSURL *)fileURL error:(NSError **)error
{
const char *path = fileURL.fileSystemRepresentation;
const ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0);
if (length <= 0) {
if (error) *error = [self errorWithPOSIXErrorNumber:errno];
return nil;
}
char *buffer = malloc(length);
ssize_t result = getxattr(path, key.UTF8String, buffer, length, 0, 0);
if (result == 0) {
return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
free(buffer);
if (error) *error = [self errorWithPOSIXErrorNumber:errno];
return nil;
}
#pragma mark - Extended Attributes - Key
- (NSString *)keyOfItemAtURL:(NSURL *)fileURL error:(NSError **)error
{
return [self attribute:KeyExtendedAttributeName ofItemAtURL:fileURL error:error];
}
- (BOOL)setKey:(NSString *)key ofItemAtURL:(NSURL *)fileURL error:(NSError **)error
{
return [self setAttribute:KeyExtendedAttributeName value:key ofItemAtURL:fileURL error:error];
[_storage removeAllObjects];
}
@end

View File

@@ -21,27 +21,36 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
RCTScrollEventTypeEndAnimation,
};
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
* provides some convenience methods for generating event calls.
*/
@interface RCTEventDispatcher : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* Send a named event. For most purposes, use the an
* event type of RCTEventTypeDefault, the other types
* are used internally by the React framework.
* Send a device or application event that does not relate to a specific
* view, e.g. rotation, location, keyboard show/hide, background/awake, etc.
*/
- (void)sendEventWithName:(NSString *)name body:(NSDictionary *)body;
- (void)sendDeviceEventWithName:(NSString *)name body:(NSDictionary *)body;
/**
* Send text events
* Send a user input event. The body dictionary must contain a "target"
* parameter, representing the react tag of the view sending the event
*/
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
/**
* Send a text input/focus event.
*/
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text;
/**
* Send scroll events
* (You can send a fake scroll event by passing nil for scrollView)
* Send a scroll event.
* (You can send a fake scroll event by passing nil for scrollView).
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag

View File

@@ -4,7 +4,6 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "UIView+ReactKit.h"
@implementation RCTEventDispatcher
{
@@ -19,7 +18,14 @@
return self;
}
- (void)sendEventWithName:(NSString *)name body:(NSDictionary *)body
- (void)sendDeviceEventWithName:(NSString *)name body:(NSDictionary *)body
{
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a react tag");
@@ -40,7 +46,7 @@
@"topEndEditing",
};
[self sendEventWithName:events[type] body:@{
[self sendInputEventWithName:events[type] body:@{
@"text": text,
@"target": reactTag
}];
@@ -91,7 +97,7 @@
body = mutableBody;
}
[self sendEventWithName:events[type] body:body];
[self sendInputEventWithName:events[type] body:body];
}
@end

View File

@@ -7,9 +7,12 @@
// TODO: something a bit more sophisticated
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);
@implementation RCTImageDownloader
{
RCTCache *_cache;
NSMutableDictionary *_pendingBlocks;
}
+ (instancetype)sharedInstance
@@ -27,6 +30,7 @@
{
if ((self = [super init])) {
_cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"];
_pendingBlocks = [NSMutableDictionary dictionary];
}
return self;
}
@@ -36,8 +40,7 @@
return url.absoluteString;
}
- (id)_downloadDataForURL:(NSURL *)url
block:(RCTDataDownloadBlock)block
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
{
NSString *cacheKey = [self cacheKeyForURL:url];
@@ -45,36 +48,58 @@
__block NSURLSessionDataTask *task = nil;
dispatch_block_t cancel = ^{
cancelled = YES;
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
[pendingBlocks removeObject:block];
if (task) {
[task cancel];
task = nil;
}
};
if ([_cache hasDataForKey:cacheKey]) {
[_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) {
if (cancelled) return;
block(data, nil);
}];
NSMutableArray *pendingBlocks = _pendingBlocks[cacheKey];
if (pendingBlocks) {
[pendingBlocks addObject:block];
} else {
task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data, error);
}];
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
[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];
}
- (id)downloadDataForURL:(NSURL *)url
block:(RCTDataDownloadBlock)block
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
{
NSString *cacheKey = [self cacheKeyForURL:url];
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(NSData *data, NSError *error) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:data forKey:cacheKey];
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
if (!cached) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:data forKey:cacheKey];
}
dispatch_async(dispatch_get_main_queue(), ^{
block(data, error);
@@ -82,52 +107,51 @@
}];
}
- (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
{
NSString *cacheKey = [self cacheKeyForURL:url];
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(NSData *data, NSError *error) {
if (data) {
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
CGSize imageSize = size;
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
imageSize = image.size;
}
CGFloat imageScale = scale;
if (imageScale == 0 || imageScale > image.scale) {
imageScale = image.scale;
}
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
[image drawInRect:(CGRect){{0, 0}, imageSize}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:UIImagePNGRepresentation(image) forKey:cacheKey];
}
dispatch_async(dispatch_get_main_queue(), ^{
block(image, nil);
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
if (!data) {
return dispatch_async(dispatch_get_main_queue(), ^{
block(nil, error);
});
}
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
CGSize imageSize = size;
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
imageSize = image.size;
}
CGFloat imageScale = scale;
if (imageScale == 0 || imageScale > image.scale) {
imageScale = image.scale;
}
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
[image drawInRect:(CGRect){{0, 0}, imageSize}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (!cached) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:UIImagePNGRepresentation(image) forKey:cacheKey];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
block(image, nil);
});
}];
}
- (void)cancelDownload:(id)downloadToken
{
if (downloadToken) {
dispatch_block_t block = downloadToken;
dispatch_block_t block = (id)downloadToken;
block();
}
}