SDWebImage is no stranger to iOS developers. This framework implements an asynchronous image download and supports caching by adding categories to UIImageView and UIButton. The interface of the whole framework is very simple, and the division of labor of each class is very clear, which is worth learning.

To use the framework, provide a url and a placeholder to retrieve the downloaded image in the callback:

[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
        
        imageview.image = image;
        NSLog(@" Image loading completed");
        
}];
Copy the code

We also have the flexibility of not setting placeholder images and not using callback blocks:

// Display the downloaded image directly after downloading
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"]].Copy the code

To start with a brief introduction to the framework:

The core class of the framework is SDWebImageManger, and externally UIImageView+WebCache and UIButton+WebCache provide interfaces for downloading images. Internal SDWebImageManger handles and coordinates SDWebImageDownloader and SDWebImageCache: The SDWebImageDownloader is responsible for the specific download tasks, and the SDWebImageCache is responsible for the work related to the cache: adding, deleting, and querying the cache.

First let’s take a look at the framework’s invocation flowchart:

As can be seen from this flowchart, the framework is divided into two layers: UIKit layer (responsible for receiving download parameters) and tools layer (responsible for download operations and caching).

OK ~ the basic process is probably clear, let’s take a look at the concrete implementation of each layer ~


UIKit layer

The outermost class of the framework is UIImageView +WebCache, and we give the URL of the image, the placeholder image directly to this class. Here is the public interface for this class:

 // ============== UIImageView + WebCache.h ============== //
- (void)sd_setImageWithURL:(nullable NSURL *)url;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options;

- (void)sd_setImageWithURL:(nullable NSURL *)url
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;
Copy the code

As you can see, this class provides a very flexible interface to call one of these methods according to our own needs, and these methods eventually lead to:

// ============== UIView+ WebCache.m ============== //
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;
Copy the code

This method calls UIView+WebCache:

// ============== UIView+ WebCache.m ============== //
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock;
Copy the code

Why not UIImageView+WebCache instead of going one level up into UIView’s category? Since the SDWebImage framework also supports UIButton’s image download methods, there needs to be a unified download method in their parent class: UIView.

A quick look at the implementation of this method (the omitted code uses… Instead of) :

 // ============== UIView+ WebCache.m ============== //

    / / valid key: UIImageView | | UIButton
    NSString*validOperationKey = operationKey ? :NSStringFromClass([self class]);
    / / UIView + WebCacheOperation operationDictionary
    // The following line of code ensures that no asynchronous download operation is currently in progress so that it does not conflict with an upcoming operation
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    

    // Add temporary placeholders (under option not to delay adding placeholders)
    if(! (options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    // If the URL exists
    if (url) {
       
       ...
        __weak __typeof(self)wself = self;

       //SDWebImageManager downloads the image
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
          
            ...
            //dispatch_main_sync_safe: ensure that blocks can be performed on the main thread
            dispatch_main_async_safe(^{
                
                if(! sself) {return;
                }
               
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                     // placeholder image, and not automatically replace placeholder image
                    completedBlock(image, error, cacheType, url);
                    return;
                    
                } else if (image) {
                    Placeholder image; placeholder image; placeholder image; placeholder image
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                
                } else {                    
                    // Placeholder image will be displayed after the image is loaded
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nilbasedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; }}if(completedBlock && finished) { completedBlock(image, error, cacheType, url); }}); }];// Add operation to operationDictionary to indicate that the current operation is in progress
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        
    } else {
        // If the URL doesn't exist, pass an error in completedBlock (the URL is empty)
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:- 1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url); }}); It is worth mentioning that in this layer, a dictionary operationDictionary is used exclusively as a cache for storage operations, adding and deleting operation tasks at any time. And this dictionary is' 'UIViewAn associated object of the WebCacheOperation class whose access method is operated using the runtime: objc// ============== UIView+WebCacheOperation.m ============== //
 // Get the associated object: operations (dictionary used to store operations)
- (SDOperationsDictionary *)operationDictionary {
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    // A dictionary to store operations
    if (operations) {
        return operations;
    }
    
    // If not, create a new one
    operations = [NSMutableDictionary dictionary];
    
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
Copy the code

Why not just associate this object directly in UIImageView+WebCache? I think the author is following the SRP: Single responsibility principle. Even classes fulfill this responsibility, let alone classification. Here the author specifically creates a class UIView+WebCacheOperation to manage the operation cache (dictionary).

So now that I’ve done everything above the UIKit layer, let’s start talking about the tools layer.

Tools layer

As mentioned earlier, the SDWebImageManager manages both the SDImageCache and SDWebImageDownloader classes and is the big brother of this layer. At the beginning of the download task, the SDWebImageManager first accesses the SDImageCache to see if there is a cache, and if there is a cache, returns the cached image directly. If there is no cache, run the SDWebImageDownloader command to download the image. After the download is successful, the image is saved to the cache and displayed. The above is the general workflow of SDWebImageManager.

Before going into the details of how SDWebImageManager downloads images, let’s take a look at a few important attributes of this class:

 // ============== SDWebImageManager.h ============== //
@property (strong.nonatomic.readwrite.nonnull) SDImageCache *imageCache;// Manage the cache
@property (strong.nonatomic.readwrite.nonnull) SDWebImageDownloader // imageDownloader;
@property (strong.nonatomic.nonnull) NSMutableSet<NSURL *> *failedURLs;// Record the list of invalid urls
@property (strong.nonatomic.nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;// Record the current operation being performed
Copy the code

SDWebImageManager has only one method for downloading images:

[SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]
Copy the code

Take a look at an implementation of this method:

 // ============== SDWebImageManager.m ============== //
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
     ...                             
    // Check SDImageCache to see if there are cached images
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        
        ...
        / / (no cache) | | (even with the cache pictures, pictures also need to update the cache) | | (agent no response imageManager: shouldDownloadImageForURL: message, the default returns yes, Need to download the pictures) | | (imageManager: shouldDownloadImageForURL: returns yes, need to download the pictures)
        if((! cachedImage || options & SDWebImageRefreshCached) && (! [self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            
            //1. There are cached images && Even if there are cached images, download updated images
            if (cachedImage && options & SDWebImageRefreshCached) {
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }


            // 2. If there is no cached image.// Start the download
            //subOperationToken is used to mark the current download task for cancellation
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if(! strongOperation || strongOperation.isCancelled) {// 1. If the task is cancelled, do nothing to avoid repeating with other Completedblocks
                
                } else if (error) {
                    
                    //2. If there is an error
                    Pass an error in completedBlock
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

							//2.2 Add the current URL to the error URL list
                    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]; }}}else {
                    
                    //3. The download is successful
                    //3.1 If you need to download the url again after a download failure, remove the current URL from the failed URL list
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url]; }}//3.2 Cache
                    BOOLcacheOnDisk = ! (options & SDWebImageCacheMemoryOnly);if(options & SDWebImageRefreshCached && cachedImage && ! downloadedImage) {// (refresh the image even if the cache exists.) && Cached image && No downloaded image: no operation
                                           
                    } else if(downloadedImage && (! downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        
          / / (download images successfully && (without moving figure | | processing dynamic figure) && (after download, Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOLimageWasTransformed = ! [transformedImage isEqual:downloadedImage];// pass nil if the image was transformed, so we can recalculate the data from the image
                                // Cache images
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            // Pass the picture to completedBlock
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        
                        //(image downloaded successfully and finished)
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nilcacheType:SDImageCacheTypeNone finished:finished url:url]; }}// If done, remove the current operation from the list of currently running operations
                if (finished) {
                    [selfsafelyRemoveOperationFromRunning:strongOperation]; }}];// Cancel block
            operation.cancelBlock = ^{
            
                // Cancel the current token
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                // Remove the current operation from the list of currently running operations
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        
        } else if (cachedImage) {
            
            // There are cached images
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            
            // Call the completed block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            
            // Delete the current download operation (thread-safe)
            [self safelyRemoveOperationFromRunning:operation];
        
        } else {
            
            // There are no cached images, and the download was terminated by the proxy
            __strong __typeof(weakOperation) strongOperation = weakOperation;
           
            // Call the completed block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            
            // Delete the current download operation
            [selfsafelyRemoveOperationFromRunning:operation]; }}];return operation;                                                             
}
Copy the code

After looking at the callback handling of SDWebImageManager, let’s take a look at SDImageCache and SDWebImageDownloader respectively. First look at SDImageCache:

SDImageCache

attribute

 // ============== SDImageCache.m ============== //
@property (strong.nonatomic.nonnull) NSCache *memCache;// Memory cache
@property (strong.nonatomic.nonnull) NSString *diskCachePath;// Disk cache path
@property (strong.nonatomic.nullable) NSMutableArray<NSString *> *customPaths;//
@property (SDDispatchQueueSetterSementics, nonatomic.nullable) dispatch_queue_t //ioQueue has a unique child thread;
Copy the code

Core method: query cache

 // ============== SDImageCache.m ============== //
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
   
    if(! key) {if (doneBlock) {
            doneBlock(nil.nil, SDImageCacheTypeNone);
        }
        return nil;
    }
		
    //================ Check the cache of memory =================//
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    // If so, call block to pass image, data, CaheType
    if (image) {
    
        NSData *diskData = nil;
        
        // If it is a GIF, take the data and pass it to doneBlock. Either GIF or nil
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        
        // Because images have caches to use, NSOperation is not instantiated. Scope nil
        return nil;
    }

    //================ Check the disk cache =================//
    NSOperation *operation = [NSOperation new];
    
    // Only child thread: self.ioqueue
    dispatch_async(self.ioQueue, ^{
        
        if (operation.isCancelled) {
            // The author is very careful to determine whether operation is cancelled before using it
            return;
        }

        @autoreleasepool {
            
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                
                // cost is used to calculate the cost of all objects in the cache. When memory is limited or the total cost of all cached objects exceeds the maximum allowed value, the cache removes some of them.
                NSUInteger cost = SDCacheCostForImage(diskImage);
                
                // Store it in memory cache
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); }}});return operation;
}
Copy the code

SDWebImageDownloader

attribute

 // ============== SDWebImageDownloader.m ============== //
@property (strong.nonatomic.nonnull) NSOperationQueue *downloadQueue;// Download the queue
@property (weak.nonatomic.nullable) NSOperation *lastAddedOperation;// The download operation added last
@property (assign.nonatomic.nullable) Class operationClass;/ / operation
@property (strong.nonatomic.nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;// Manipulate arrays
@property (strong.nonatomic.nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;/ / the HTTP request header
@property (SDDispatchQueueSetterSementics, nonatomic.nullable) dispatch_queue_t barrierQueue;// Block the previous download thread (serialization)
Copy the code

Core method: download images

 // ============== SDWebImageDownloader.m ============== //
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        
        __strong __typeof (wself) sself = wself;
        
        NSTimeInterval timeoutInterval = sself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        
        // Create download request
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        
        / / create a download operation: SDWebImageDownloaderOperation used to request the operation of the network resources, it is a subclass of NSOperationSDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session  options:options]; operation.shouldDecompressImages = sself.shouldDecompressImages;/ / url certificate
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        / / priority
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        // Add the download operation to the download queue
        [sself.downloadQueue addOperation:operation];
        
        // If last in, first out
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            //addDependency: After the opertaion parameter is added to NSOperationQueue, other operations can be performed only after the opertion finishes
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}
Copy the code

There is also an addProgressCallback: progressBlock: completedBlock: forURL: createCallback: method that holds progressBlock and completedBlock. Let’s look at the implementation of this method:

 // ============== SDWebImageDownloader.m ============== //
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {

    // the url is used as the key of the callback dictionary. If it is empty, failure is immediately returned
    if (url == nil) {
        if(completedBlock ! =nil) {
            completedBlock(nil.nil.nil.NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    // serialize all previous operations
    dispatch_barrier_sync(self.barrierQueue, ^{
    
        	/ / the current download operation to retrieve SDWebImageDownloaderOperation instance
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        
        if(! operation) {// If not, initialize it
            operation = createCallback();
            self.URLOperations[url] = operation;
            __weak SDWebImageDownloaderOperation *woperation = operation;
            
            operation.completionBlock = ^{
              SDWebImageDownloaderOperation *soperation = woperation;
              if(! soperation)return;
              if (self.URLOperations[url] == soperation) {
                  [self.URLOperations removeObjectForKey:url];
              };
            };
        }
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        / / here downloadOperationCancelToken default is a dictionary, deposit progressBlock and completedBlock
        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

Copy the code

The method that actually saves the two blocks here is addHandlersForProgress: Completed: :

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // Instantiate a SDCallbacksDictionary that holds a progressBlock and completedBlock
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    dispatch_barrier_async(self.barrierQueue, ^{
        // Add self.callbackblocks to the cache
        [self.callbackBlocks addObject:callbacks];
    });
    return callbacks;
}

Copy the code

The core methods of SDWebImage are all covered here, and the rest will be added later.

Finally, let’s take a look at some scattered points:


1. Runtime access to associated objects:

Deposit:

objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Associate the operations object with self at the address &loadOperationKey with the semantics OBJC_ASSOCIATION_RETAIN_NONATOMIC.
Copy the code

Pick up:

SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
// Remove the Operations object from self with the address &loadOperationKey
Copy the code

2. Array write operations need to lock (multithreaded access, avoid overwriting)


// Lock self.runningOperations
//self.runningOperations Adds an array
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

// Self. runningOperations Deletes the array
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation]; }}}Copy the code

3. Make sure macros in the main thread:

dispatch_main_async_safe(^{
 				 // Put the following code in the main thread
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });

// macro definition:
#define dispatch_main_async_safe(block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\ block(); The \}else {\
        dispatch_async(dispatch_get_main_queue(), block); The \}#endif
Copy the code

4. Set parameters that cannot be nil

- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}
Copy the code

If the nonnull keyword is added to the argument, the compiler checks to see if the argument passed is nil, and raises a warning if it is

5. Fault tolerance, cast type

if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
}
Copy the code

When an NSString argument is passed in (but the method argument is required to be NSURL), it is automatically converted to NSURL


There seems to be a picture decoding and other content did not look in detail, will gradually supplement ~

This post has been synced to my personal tech blog: Portal

— — — — — — — — — — — — — — — — — — — — — — — — — — — — on July 17, 2018 update — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Pay attention!!

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you