Эх сурвалжийг харах

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

DreamPiggy 8 жил өмнө
parent
commit
db5307eb94

+ 2 - 4
SDWebImage/SDImageCache.h

@@ -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

+ 26 - 14
SDWebImage/SDImageCache.m

@@ -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;
     }
     

+ 0 - 2
SDWebImage/SDWebImageImageIOCoder.m

@@ -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);
     }

+ 10 - 5
Tests/Tests/SDImageCacheTests.m

@@ -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];

+ 2 - 1
Tests/Tests/SDMockFileManager.h

@@ -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

+ 14 - 13
Tests/Tests/SDMockFileManager.m

@@ -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