Change our imageCache `storeImageDataToDisk` to internal use IO-queue. And also change error from POSIX errno to Cocoa file error

This commit is contained in:
DreamPiggy 2017-12-30 02:16:52 +08:00
parent 7be0bbe331
commit db5307eb94
6 changed files with 54 additions and 39 deletions

View File

@ -150,15 +150,13 @@ typedef void(^SDWebImageCompletionWithPossibleErrorBlock)(NSError * _Nullable er
/**
* Synchronously store image NSData into disk cache at the given key.
*
* @warning This method is synchronous, make sure to call it from the ioQueue
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
* @param errorPtr NSError pointer. If error occurs then (*errorPtr) != nil.
* @param error NSError pointer, for possible file I/O errors, See FoundationErrors.h
*/
- (BOOL)storeImageDataToDisk:(nullable NSData *)imageData
forKey:(nullable NSString *)key
error:(NSError * _Nullable * _Nullable)errorPtr;
error:(NSError * _Nullable * _Nullable)error;
#pragma mark - Query and Retrieve Ops

View File

@ -137,14 +137,6 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
#pragma mark - Cache paths
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
@ -233,7 +225,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
}
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
[self storeImageDataToDisk:data forKey:key error:&writeError];
[self safeStoreImageDataToDisk:data forKey:key error:&writeError];
}
if (completionBlock) {
@ -251,15 +243,34 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
- (BOOL)storeImageDataToDisk:(nullable NSData *)imageData
forKey:(nullable NSString *)key
error:(NSError * _Nullable * _Nullable)errorPtr {
error:(NSError * _Nullable __autoreleasing * _Nullable)error {
if (!imageData || !key) {
return NO;
}
__autoreleasing NSError *fileError;
if (!error) {
error = &fileError;
}
[self checkIfQueueIsIOQueue];
__block BOOL success = YES;
void(^storeImageDataBlock)(void) = ^{
success = [self safeStoreImageDataToDisk:imageData forKey:key error:error];
};
dispatch_sync(self.ioQueue, storeImageDataBlock);
return success;
}
- (BOOL)safeStoreImageDataToDisk:(nullable NSData *)imageData
forKey:(nullable NSString *)key
error:(NSError * _Nullable __autoreleasing * _Nonnull)error {
if (!imageData || !key) {
return NO;
}
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
if (![_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:error]) {
return NO;
}
}
// get cache Path for image key
@ -267,8 +278,9 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
if (![_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil] && errorPtr) {
*errorPtr = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
// NSFileManager's `createFileAtPath:` is used just for old code compatibility and will not trigger any delegate methods, so it's useless for custom NSFileManager at all.
// And also, NSFileManager's `createFileAtPath:` can only grab underlying POSIX errno, but NSData can grab errors defined in NSCocoaErrorDomain, which is better for user to check.
if (![imageData writeToFile:cachePathForKey options:NSDataWritingAtomic error:error]) {
return NO;
}

View File

@ -540,8 +540,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
} // else - if it's not set it remains at up
CFRelease((CFTypeRef) properties);
} else {
//NSLog(@"NO PROPERTIES, FAIL");
}
CFRelease(imageSource);
}

View File

@ -309,23 +309,28 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
- (void)test41StoreImageDataToDiskWithError {
NSData *imageData = [NSData dataWithContentsOfFile:[self testImagePath]];
NSError * error = nil;
NSError *targetError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteNoPermissionError userInfo:nil];
NSError *error = nil;
SDMockFileManager *fileManager = [[SDMockFileManager alloc] init];
fileManager.mockSelectors = @{NSStringFromSelector(@selector(createDirectoryAtPath:withIntermediateDirectories:attributes:error:)) : targetError};
SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"test"
diskCacheDirectory:@"/"
fileManager:[[SDMockFileManager alloc] initWithError:EACCES]];
fileManager:fileManager];
[cache storeImageDataToDisk:imageData
forKey:kImageTestKey
error:&error];
XCTAssertEqual(error.code, EACCES);
XCTAssertEqual(error.code, NSFileWriteNoPermissionError);
}
- (void)test42StoreImageDataToDiskWithoutError {
NSData *imageData = [NSData dataWithContentsOfFile:[self testImagePath]];
NSError * error = nil;
NSError *error = nil;
SDMockFileManager *fileManager = [[SDMockFileManager alloc] init];
fileManager.mockSelectors = @{NSStringFromSelector(@selector(createDirectoryAtPath:withIntermediateDirectories:attributes:error:)) : [NSNull null]};
SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"test"
diskCacheDirectory:@"/"
fileManager:[[SDMockFileManager alloc] initWithError:0]];
fileManager:fileManager];
[cache storeImageDataToDisk:imageData
forKey:kImageTestKey
error:&error];

View File

@ -8,8 +8,9 @@
#import <Foundation/Foundation.h>
// This is a mock class to provide custom error for methods
@interface SDMockFileManager : NSFileManager
- (id)initWithError:(int)errorNumber;
@property (nonatomic, copy, nullable) NSDictionary<NSString *, NSError *> *mockSelectors; // used to specify mocked selectors which will return NO with specify error instead of normal process. If you specify a NSNull, will use nil instead.
@end

View File

@ -10,24 +10,25 @@
@interface SDMockFileManager ()
@property (nonatomic, assign) int errorNumber;
@end
@implementation SDMockFileManager
- (id)initWithError:(int)errorNumber {
self = [super init];
if (self) {
_errorNumber = errorNumber;
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary<NSFileAttributeKey,id> *)attributes error:(NSError * _Nullable __autoreleasing *)error {
NSError *mockError = [self.mockSelectors objectForKey:NSStringFromSelector(_cmd)];
if ([mockError isEqual:[NSNull null]]) {
if (error) {
*error = nil;
}
return NO;
} else if (mockError) {
if (error) {
*error = mockError;
}
return NO;
} else {
return [super createDirectoryAtPath:path withIntermediateDirectories:createIntermediates attributes:attributes error:error];
}
return self;
}
- (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary<NSString *,id> *)attr {
errno = self.errorNumber;
return (self.errorNumber == 0);
}
@end