[React Native] Replace RCTCache with NSURLCache

This commit is contained in:
Alex Akers 2015-06-29 05:15:25 -07:00
Родитель c953aa7e0b
Коммит 454b5f3c0b
5 изменённых файлов: 94 добавлений и 367 удалений

Просмотреть файл

@ -14,7 +14,7 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
@interface RCTImageDownloader : NSObject
+ (instancetype)sharedInstance;
+ (RCTImageDownloader *)sharedInstance;
/**
* Downloads a block of raw data and returns it. Note that the callback block

Просмотреть файл

@ -9,25 +9,27 @@
#import "RCTImageDownloader.h"
#import "RCTCache.h"
#import "RCTLog.h"
#import "RCTUtils.h"
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);
CGSize RCTTargetSizeForClipRect(CGRect);
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
@implementation RCTImageDownloader
{
RCTCache *_cache;
NSURLCache *_cache;
dispatch_queue_t _processingQueue;
NSMutableDictionary *_pendingBlocks;
}
+ (instancetype)sharedInstance
+ (RCTImageDownloader *)sharedInstance
{
static RCTImageDownloader *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
sharedInstance = [[RCTImageDownloader alloc] init];
});
return sharedInstance;
}
@ -35,27 +37,22 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
- (instancetype)init
{
if ((self = [super init])) {
_cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"];
_cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"];
_processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
_pendingBlocks = [[NSMutableDictionary alloc] init];
}
return self;
}
static NSString *RCTCacheKeyForURL(NSURL *url)
{
return url.absoluteString;
return self;
}
- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block
{
NSString *cacheKey = RCTCacheKeyForURL(url);
NSString *cacheKey = url.absoluteString;
__block BOOL cancelled = NO;
__block NSURLSessionDataTask *task = nil;
dispatch_block_t cancel = ^{
cancelled = YES;
dispatch_async(_processingQueue, ^{
@ -88,21 +85,28 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
});
};
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 = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!cancelled) {
runBlocks(NO, data, error);
}
[task resume];
}
RCTImageDownloader *strongSelf = weakSelf;
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
[strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task];
task = nil;
}];
[_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) {
if (cancelled) {
return;
}
if (cachedResponse) {
runBlocks(YES, cachedResponse.data, nil);
} else {
[task resume];
}
}];
}
});
@ -111,22 +115,78 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
{
NSString *cacheKey = RCTCacheKeyForURL(url);
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
if (!cached) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:data forKey:cacheKey];
}
block(data, error);
}];
}
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
if (!data || error) {
block(nil, error);
return;
}
if (CGSizeEqualToSize(size, CGSizeZero)) {
// Target size wasn't available yet, so abort image drawing
block(nil, nil);
return;
}
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
// Get scale and size
CGFloat destScale = scale ?: RCTScreenScale();
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
// Opacity optimizations
UIColor *blendColor = nil;
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
if (!opaque && backgroundColor) {
CGFloat alpha;
[backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha];
if (alpha > 0.999) { // no benefit to blending if background is translucent
opaque = YES;
blendColor = backgroundColor;
}
}
// Decompress image at required size
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
if (blendColor) {
[blendColor setFill];
UIRectFill((CGRect){CGPointZero, destSize});
}
[image drawInRect:imageRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
block(image, nil);
}];
}
- (void)cancelDownload:(id)downloadToken
{
if (downloadToken) {
((dispatch_block_t)downloadToken)();
}
}
@end
/**
* Returns the optimal context size for an image drawn using the clip rect
* returned by RCTClipRect.
*/
CGSize RCTTargetSizeForClipRect(CGRect);
CGSize RCTTargetSizeForClipRect(CGRect clipRect)
{
return (CGSize){
@ -141,7 +201,6 @@ CGSize RCTTargetSizeForClipRect(CGRect clipRect)
* then calculates the optimal rectangle to draw the image into so that it will
* be sized and positioned correctly if drawn using the specified content mode.
*/
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
@ -202,66 +261,3 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
return (CGRect){CGPointZero, destSize};
}
}
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
if (!data || error) {
block(nil, error);
return;
}
if (CGSizeEqualToSize(size, CGSizeZero)) {
// Target size wasn't available yet, so abort image drawing
block(nil, nil);
return;
}
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
// Get scale and size
CGFloat destScale = scale ?: RCTScreenScale();
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
// Opacity optimizations
UIColor *blendColor = nil;
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
if (!opaque && backgroundColor) {
CGFloat alpha;
[backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha];
if (alpha > 0.999) { // no benefit to blending if background is translucent
opaque = YES;
blendColor = backgroundColor;
}
}
// Decompress image at required size
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
if (blendColor) {
[blendColor setFill];
UIRectFill((CGRect){CGPointZero, destSize});
}
[image drawInRect:imageRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
block(image, nil);
}];
}
- (void)cancelDownload:(id)downloadToken
{
if (downloadToken) {
((dispatch_block_t)downloadToken)();
}
}
@end

Просмотреть файл

@ -1,29 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@interface RCTCache : NSObject
- (instancetype)init; // name = @"default"
- (instancetype)initWithName:(NSString *)name;
@property (nonatomic, assign) NSUInteger maximumDiskSize; // in bytes
#pragma mark - Retrieval
- (BOOL)hasDataForKey:(NSString *)key;
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *data))completionHandler;
#pragma mark - Insertion
- (void)setData:(NSData *)data forKey:(NSString *)key;
- (void)removeAllData;
@end

Просмотреть файл

@ -1,234 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTCache.h"
#import <UIKit/UIKit.h>
#import <sys/xattr.h>
#import "RCTAssert.h"
static NSString *const RCTCacheSubdirectoryName = @"React";
static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.React.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 (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.React.RCTCache.%@", _UUID.UUIDString];
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
_queue = queue;
}
dispatch_async(queue, block);
}
@end
#pragma mark - Cache
@implementation RCTCache
{
NSString *_name;
NSFileManager *_fileManager;
NSMutableDictionary *_storage;
NSURL *_cacheDirectoryURL;
}
+ (void)initialize
{
if (self == [RCTCache class]) {
RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable];
}
}
- (instancetype)init
{
return [self initWithName:@"default"];
}
- (instancetype)initWithName:(NSString *)name
{
RCTAssertParam(name);
RCTAssert(name.length < NAME_MAX, @"Name must be fewer than %i characters in 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: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;
NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL);
if (!key) {
[_fileManager removeItemAtURL:fileURL error:NULL];
continue;
}
_storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID];
}
}
return self;
}
- (BOOL)hasDataForKey:(NSString *)key
{
return _storage[key] != nil;
}
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler
{
NSParameterAssert(key.length > 0);
NSParameterAssert(completionHandler != nil);
RCTCacheRecord *record = _storage[key];
if (!record) {
completionHandler(nil);
return;
}
[record enqueueBlock:^{
if (!record.data) {
record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]];
}
completionHandler(record.data);
}];
}
- (void)setData:(NSData *)data forKey:(NSString *)key
{
NSParameterAssert(key.length > 0);
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) {
[data writeToURL:fileURL options:NSDataWritingAtomic error:NULL];
RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL);
} else {
[_fileManager removeItemAtURL:fileURL error:NULL];
}
if (identifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:identifier];
}
}];
}
- (void)removeAllData
{
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
dispatch_group_t group = dispatch_group_create();
for (RCTCacheRecord *record in _storage.allValues) {
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
dispatch_group_async(group, record.queue, ^{
[_fileManager removeItemAtURL:fileURL error:NULL];
});
}
if (identifier != UIBackgroundTaskInvalid) {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:identifier];
});
}
[_storage removeAllObjects];
}
@end

Просмотреть файл

@ -67,7 +67,6 @@
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; };
783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; };
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; };
@ -221,8 +220,6 @@
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
830BA4531A8E3BDA00D53203 /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = "<group>"; };
830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = "<group>"; };
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = "<group>"; };
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = "<group>"; };
83CBBA2E1A601D0E00E9B192 /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
@ -427,8 +424,6 @@
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */,
830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
830BA4531A8E3BDA00D53203 /* RCTCache.h */,
830BA4541A8E3BDA00D53203 /* RCTCache.m */,
83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
13AF1F851AE6E777005F5298 /* RCTDefines.h */,
@ -605,7 +600,6 @@
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */,
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */,
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */,
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */,