IOS SDWebImage 2.X Source Reading (IV)

(6) In the previous article, we talked about downloading pictures. Below we can see that after downloading pictures, callback related block s

 operation = [[wself.operationClass alloc] initWithRequest:request
                                                          options:options
                                                          progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                 SDWebImageDownloader *sself = wself;
                 if (!sself) return;
                 __block NSArray *callbacksForURL;
//                  The barrierQueue is designed to ensure that only one thread operates on URLCallbacks at a time
                 dispatch_sync(sself.barrierQueue, ^{
                     callbacksForURL = [sself.URLCallbacks[url] copy];
                 });
                 for (NSDictionary *callbacks in callbacksForURL) {
                     dispatch_async(dispatch_get_main_queue(), ^{
                         SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                         //Callback if a user customizes a block
                         if (callback) callback(receivedSize, expectedSize);
                     });
                 }
             }

This section is the block associated with the progress bar.
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
dispatch_sync(queue,block), synchronization, blocks the current thread until the subsequent blocks are executed, which ensures that only one thread can operate on URLCallbacks at the same time, following the addProgressCallback method with dispatch_barrier_sync for URLCallbacks

 completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                AppLog(@"Picture data download complete")
                SDWebImageDownloader *sself = wself;
                if (!sself) return;
                __block NSArray *callbacksForURL;
                //synchronizationbarrierTask will be downloadedurlAnd so on fromURLCallbacksFrom Remove
                dispatch_barrier_sync(sself.barrierQueue, ^{
                    callbacksForURL = [sself.URLCallbacks[url] copy];
                    if (finished) {
                        [sself.URLCallbacks removeObjectForKey:url];
                    }
                });
                AppLog(@"completed callbacksForURL--->%@",callbacksForURL);
                for (NSDictionary *callbacks in callbacksForURL) {
                    SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                    //block that has just been downloaded
                    if (callback) callback(image, data, error, finished);
                }
            }

1. In SWebImageDownloaderCompletedBlock, downloaded pictures are downloaded asynchronously with multiple threads, but after each thread, dispatch_barrier_sync (queue, block) is used to ensure that the previous operation is completed before the block in dispatch_barrier_sync can be executed, and the block in dispatch_barrier_sync can be executed until the block in dispatch_barrier_sync is executed.Code after.
2. After the URL download operation is completed, the relevant operations corresponding to the URL need to be removed: [sself.URLCallbacks removeObjectForKey:url];
3. Then traverse and call back the corresponding successful block

Canceled functionality is similar

cancelled:^{
                SDWebImageDownloader *sself = wself;
                if (!sself) return;
                dispatch_barrier_async(sself.barrierQueue, ^{
                    [sself.URLCallbacks removeObjectForKey:url];
                });
            }];

Let's look at the callbacks associated with the following methods

[self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished);

//operation does not exist, or cancels, and does nothing, because if we call completedBlock, we may call it elsewhere, or overwrite the data
__strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {

                }
                else if (error) {//Download failed
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }

Download related block s for other situations completed

AppLog(@"SDWebImageManager Download Successful Callback completedBlock");
if ((options & SDWebImageRetryFailed)) {
      @synchronized (self.failedURLs) {
             [self.failedURLs removeObject:url];
      }
}

If the SDWebImageRetryFailed property is set, the url needs to be removed from the failedURLs so that it can be downloaded again

 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    AppLog(@"Is cached on disk: cacheOnDisk = %d",cacheOnDisk);
                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block Image Refresh HitNSURLCacheCache, do not call completion block
                    }

Do you need to cache in the disk cache
If the picture hits the NSURLCache cache, you do not need to call the complete block

else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        AppLog(@"gif Image related operations???");
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }

1. Downloaded Image with Downloaded Image
(downloadedImage is not a gif motion picture|| (SDWebImageTransformAnimatedImage is set to animate it)
3. Implement imageManager:transformDownloadedImage:withURL:Method

else {

                        if (downloadedImage && finished) {//Image exists and download is complete
                            AppLog(@"Image exists and download complete Store image on disk");
                            //Store images on disk
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }

                        dispatch_main_sync_safe(^{
                            if (strongOperation && !strongOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }

Image exists and download completed: Store picture on disk
Callback completeBlock in main thread to display picture

if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                        //When the execution is complete, the picture is successful and you can set the current one operation Removed.
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }

Download complete, lock self.runningOperations
When the execution is complete and the picture is successful, you can remove the current operation.

See how to store images in a cache
- (void)storeImage:(UIImage )image recalculateFromImage:(BOOL)recalculate imageData:(NSData )imageData forKey:(NSString )key toDisk:(BOOL)toDisk;*

if (!image || !key) {
        return;
    }
    // if memory cache is enabled
    if (self.shouldCacheImagesInMemory) {
        AppLog(@"Store images in memory...");
        NSUInteger cost = SDCacheCostForImage(image);//Calculate the size of the picture storage
        [self.memCache setObject:image forKey:key cost:cost];//Store images in memory
    }

1. Picture does not exist || key does not exist, return
2. Use memory cache or not, default YES, store pictures in memory cache

Open a new thread to asynchronously cache pictures on disk

dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;
            //recalculate: Indicates whether imageData can be used or whether it should be retrieved fromUIImageConstruct new data
             //The imageData is empty (if the downloaded image needs to be transform ed, the imageData will be empty)
            if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
                //IfimageDataIs zero (that is, if you try to save directly)UIImageOr convert the image when downloading) and the image hasalphaChannel, we'll think it'sPNGTo avoid losing transparency
                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);//Image Transparency
                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||
                                  alphaInfo == kCGImageAlphaNoneSkipLast);
                BOOL imageIsPng = hasAlpha;

                // But if we have an image data, we will look at the preffix
                //PNGThe first eight bytes of a file always contain the same signature
                if ([imageData length] >= [kPNGSignatureData length]) {
                    imageIsPng = ImageDataHasPNGPreffix(imageData);
                }

                //image is png
                if (imageIsPng) {
                    data = UIImagePNGRepresentation(image);
                }
                else {//image is jpeg, compression quality is1,No compression
                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
                }
#else
//Other platforms, no longer on the iPhone, use the following method
                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
            }

Cache pictures on disk if they still exist after data processing

 if (data) {
                if (![_fileManager fileExistsAtPath:_diskCachePath]) {//Check if the path exists
                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                }

                // get cache Path for image key
                NSString *cachePathForKey = [self defaultCachePathForKey:key];
                AppLog(@"cachePathForKey-->%@",cachePathForKey);
                // transform to NSUrl
                NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
                AppLog(@"fileURL-->%@",fileURL);
                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];//Store picture data

                // disable iCloud backup
               if (self.shouldDisableiCloud) {//icloud backup, icloud backup is disabled by default
                    [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
                }
            }

1,cachePathForKey:
/Users/xxx/Library/Developer/CoreSimulator/Devices/483F34CE-F906-4FE5-BD3A-A758DCEEB118/data/Containers/Data/Application/69141B68-DCDB-4991-A07A-0592290C5F63/Library/Caches/default/com.hackemist.SDWebImageCache.default/4ad9ae8eabfec60b40bf48f0bfc2d120.png
2,fileURL:
file:///Users/xxx/Library/Developer/CoreSimulator/Devices/483F34CE-F906-4FE5-BD3A-A758DCEEB118/data/Containers/Data/Application/69141B68-DCDB-4991-A07A-0592290C5F63/Library/Caches/default/com.hackemist.SDWebImageCache.default/4ad9ae8eabfec60b40bf48f0bfc2d120.png

(7) Above is to cache pictures in the memory cache and disk cache. Here's how the pictures are cleared
1. Clear memory cache

//Clear Memory Cache
- (void)clearMemory {
    [self.memCache removeAllObjects];
}

2. Clear the disk cache

//Clear Disk Cache
- (void)clearDisk {
    [self clearDiskOnCompletion:nil];
}
//Clear all disk cached images.Non-blocking method - Return immediately.
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
    dispatch_async(self.ioQueue, ^{
        //Remove data from diskCachePath folder
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        //Re-create Cache Path
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

3. Delete all expired cache images from disk.

// Remove all expired cached images from disk.
- (void)cleanDisk {
    [self cleanDiskWithCompletionBlock:nil];
}
// Remove all expired cached images from disk.Non-blocking method - Return immediately.
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        /*
         NSURLIsDirectoryKey:Determine if the traversed URL refers to a directory..
         NSURLContentModificationDateKey:Returns the last access date traversed to item content.
         NSURLTotalFileAllocatedSizeKey:Total file allocation size.The total allocated size of the file in bytes, which may include the space used by the metadata, or zero if not available.This may be smaller than the value returned by NSURLTotalFileSizeKey if the resource is compressed.
         */

        //NSDirectoryEnumeration SkipsHiddenFiles: Indicates that hidden files are not traversed
        // This enumerator prefetches useful properties for our cache files. This enumerator prefetches useful properties for our cache files.
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        //Expiration time, current time - maximum cache time (default is one week)
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            //resourceValuesForKeys: Get the file information pointed to
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

            // Skip directories.Skip following directories
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // Remove files that are older than the expiration date; get the latest modification date
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            // Store a reference to this file and account for its total size.Store a reference to this file and consider its total size.
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }

        for (NSURL *fileURL in urlsToDelete) {//Traverse expired URLs
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // If our remaining disk cache exceeds a configured maximum size, perform a second
        // Size-based cleanup pass. We delete the oldest files first. If our remaining disk cache exceeds the configured maximum size, perform a second size-based cleanup pass.Let's delete the oldest file first.
        //If our current non-expired file size is greater than our configured cache file size, the oldest file will be deleted, targeting half of the maximum cache size for this cleanup process.
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;//Target half of the maximum cache size for this cleanup process.

            // Sort the remaining cache files by their last modification time (oldest first).
            //Sort by last modification time of remaining cache files (first earliest).
            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                            }];

            // Delete files until we fall below our desired cache size.Delete files until we are below our desired cache size.
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

4. Delete images in memory and disk cache simultaneously

//Delete images from memory and disk caches simultaneously
- (void)removeImageForKey:(NSString *)key {
    [self removeImageForKey:key withCompletion:nil];
}

- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk {
    [self removeImageForKey:key fromDisk:fromDisk withCompletion:nil];
}

- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {

    if (key == nil) {
        return;
    }

    //If caching is allowed in memory, the image in the memory cache needs to be removed
    if (self.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    //If you want to delete the image from the disk cache
    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            //Path to remove disk cache
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }

}

Added by dazraf on Wed, 15 May 2019 08:16:55 +0300