AFNetworking之UIKit擴展與緩存實現(xiàn)

寫在開頭:
  • 大概回憶下,之前我們講了AFNetworking整個網(wǎng)絡(luò)請求的流程臂拓,包括request的拼接商乎,session代理的轉(zhuǎn)發(fā)距境,response的解析申尼。以及對一些bug的適配,如果你還沒有看過垫桂,可以點這里:
    AFNetworking到底做了什么?
    AFNetworking到底做了什么(二)?
  • 除此之外我們還單獨的開了一篇講了AF對https的處理:
    AFNetworking之于https認證
  • 本文將涉及部分AF對UIKit的擴展與圖片下載相關(guān)緩存的實現(xiàn)师幕,文章內(nèi)容相對獨立,如果沒看過前文诬滩,也不影響閱讀霹粥。
回到正文:

我們來看看AF對UIkit的擴展:

UIKit擴展.png
一共如上這個多類,下面我們開始著重講其中兩個UIKit的擴展:
  • 一個是我們網(wǎng)絡(luò)請求時狀態(tài)欄的小菊花疼鸟。
  • 一個是我們幾乎都用到過請求網(wǎng)絡(luò)圖片的如下一行方法:
 - (void)setImageWithURL:(NSURL *)url ;
我們開始吧:
1.AFNetworkActivityIndicatorManager

這個類的作用相當簡單后控,就是當網(wǎng)絡(luò)請求的時候,狀態(tài)欄上的小菊花就會開始轉(zhuǎn):


小菊花.png

需要的代碼也很簡單空镜,只需在你需要它的位置中(比如AppDelegate)導入類浩淘,并加一行代碼即可:

#import "AFNetworkActivityIndicatorManager.h"
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
接下來我們來講講這個類的實現(xiàn):
  • 這個類的實現(xiàn)也非常簡單,還記得我們之前講的AF對NSURLSessionTask中做了一個Method Swizzling嗎吴攒?大意是把它的resumesuspend方法做了一個替換张抄,在原有實現(xiàn)的基礎(chǔ)上添加了一個通知的發(fā)送。

  • 這個類就是基于這兩個通知和task完成的通知來實現(xiàn)的洼怔。

首先我們來看看它的初始化方法:
+ (instancetype)sharedManager {
    static AFNetworkActivityIndicatorManager *_sharedManager = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedManager = [[self alloc] init];
    });

    return _sharedManager;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //設(shè)置狀態(tài)為沒有request活躍
    self.currentState = AFNetworkActivityManagerStateNotActive;
    //開始下載通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
    //掛起通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
    //完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
    //開始延遲
    self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay;
    //結(jié)束延遲
    self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay;
    return self;
}

  • 初始化如上署惯,設(shè)置了一個state,這個state是一個枚舉:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
    //沒有請求
    AFNetworkActivityManagerStateNotActive,
    //請求延遲開始
    AFNetworkActivityManagerStateDelayingStart,
    //請求進行中
    AFNetworkActivityManagerStateActive,
    //請求延遲結(jié)束
    AFNetworkActivityManagerStateDelayingEnd
};

這個state一共如上4種狀態(tài)镣隶,其中兩種應(yīng)該很好理解泽台,而延遲開始和延遲結(jié)束怎么理解呢?

  • 原來這是AF對請求菊花顯示做的一個優(yōu)化處理矾缓,試問如果一個請求時間很短,那么菊花很可能閃一下就結(jié)束了稻爬。如果很多請求過來嗜闻,那么菊花會不停的閃啊閃,這顯然并不是我們想要的效果桅锄。

  • 所以多了這兩個參數(shù):
    1)在一個請求開始的時候琉雳,我延遲一會在去轉(zhuǎn)菊花样眠,如果在這延遲時間內(nèi),請求結(jié)束了翠肘,那么我就不需要去轉(zhuǎn)菊花了檐束。
    2)但是一旦轉(zhuǎn)菊花開始,哪怕很短請求就結(jié)束了束倍,我們還是會去轉(zhuǎn)一個時間再去結(jié)束被丧,這時間就是延遲結(jié)束的時間。

  • 緊接著我們監(jiān)聽了三個通知绪妹,用來監(jiān)聽當前正在進行的網(wǎng)絡(luò)請求的狀態(tài)甥桂。

  • 然后設(shè)置了我們前面提到的這個轉(zhuǎn)菊花延遲開始和延遲結(jié)束的時間,這兩個默認值如下:

static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;

接著我們來看看三個通知觸發(fā)調(diào)用的方法:

//請求開始
- (void)networkRequestDidStart:(NSNotification *)notification {
    
    if ([AFNetworkRequestFromNotification(notification) URL]) {
        //增加請求活躍數(shù)
        [self incrementActivityCount];
    }
}
//請求結(jié)束
- (void)networkRequestDidFinish:(NSNotification *)notification {
    //AFNetworkRequestFromNotification(notification)返回這個通知的request,用來判斷request是否是有效的
    if ([AFNetworkRequestFromNotification(notification) URL]) {
        //減少請求活躍數(shù)
        [self decrementActivityCount];
    }
}

方法很簡單邮旷,就是開始的時候增加了請求活躍數(shù)黄选,結(jié)束則減少。調(diào)用了如下兩個方法進行加減:

//增加請求活躍數(shù)
- (void)incrementActivityCount {
    
    //活躍的網(wǎng)絡(luò)數(shù)+1婶肩,并手動發(fā)送KVO
    [self willChangeValueForKey:@"activityCount"];
    @synchronized(self) {
        _activityCount++;
    }
    [self didChangeValueForKey:@"activityCount"];

    //主線程去做
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}

//減少請求活躍數(shù)
- (void)decrementActivityCount {
    [self willChangeValueForKey:@"activityCount"];
    @synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
        _activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
    }
    [self didChangeValueForKey:@"activityCount"];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}

方法做了什么應(yīng)該很容易看明白办陷,這里需要注意的是,task的幾個狀態(tài)的通知律歼,是會在多線程的環(huán)境下發(fā)送過來的民镜。所以這里對活躍數(shù)的加減,都用了@synchronized這種方式的鎖苗膝,進行了線程保護殃恒。然后回到主線程調(diào)用了updateCurrentStateForNetworkActivityChange

我們接著來看看這個方法:

- (void)updateCurrentStateForNetworkActivityChange {
    //如果是允許小菊花
    if (self.enabled) {
        switch (self.currentState) {
            //不活躍
            case AFNetworkActivityManagerStateNotActive:
                //判斷活躍數(shù),大于0為YES
                if (self.isNetworkActivityOccurring) {
                    //設(shè)置狀態(tài)為延遲開始
                    [self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
                }
                break;
            
            case AFNetworkActivityManagerStateDelayingStart:
                //No op. Let the delay timer finish out.
                break;
            case AFNetworkActivityManagerStateActive:
                if (!self.isNetworkActivityOccurring) {
                    [self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
                }
                break;
            case AFNetworkActivityManagerStateDelayingEnd:
                if (self.isNetworkActivityOccurring) {
                    [self setCurrentState:AFNetworkActivityManagerStateActive];
                }
                break;
        }
    }
}
  • 這個方法先是判斷了我們一開始設(shè)置是否需要菊花的self.enabled辱揭,如果需要离唐,才執(zhí)行。
  • 這里主要是根據(jù)當前的狀態(tài)问窃,來判斷下一個狀態(tài)應(yīng)該是什么亥鬓。其中有這么一個屬性self.isNetworkActivityOccurring:
//判斷是否活躍
 - (BOOL)isNetworkActivityOccurring {
    @synchronized(self) {
        return self.activityCount > 0;
    }
}

那么這個方法應(yīng)該不難理解了。

這個類復(fù)寫了currentState的set方法域庇,每當我們改變這個state嵌戈,就會觸發(fā)set方法,而怎么該轉(zhuǎn)菊花也在該方法中:

//設(shè)置當前小菊花狀態(tài)
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
    @synchronized(self) {
        if (_currentState != currentState) {
            //KVO
            [self willChangeValueForKey:@"currentState"];
            _currentState = currentState;
            switch (currentState) {
                //如果為不活躍
                case AFNetworkActivityManagerStateNotActive:
                    //取消兩個延遲用的timer
                    [self cancelActivationDelayTimer];
                    [self cancelCompletionDelayTimer];
                    //設(shè)置小菊花不可見
                    [self setNetworkActivityIndicatorVisible:NO];
                    break;
                case AFNetworkActivityManagerStateDelayingStart:
                    //開啟一個定時器延遲去轉(zhuǎn)菊花
                    [self startActivationDelayTimer];
                    break;
                    //如果是活躍狀態(tài)
                case AFNetworkActivityManagerStateActive:
                    //取消延遲完成的timer
                    [self cancelCompletionDelayTimer];
                    //開始轉(zhuǎn)菊花
                    [self setNetworkActivityIndicatorVisible:YES];
                    break;
                    //延遲完成狀態(tài)
                case AFNetworkActivityManagerStateDelayingEnd:
                    //開啟延遲完成timer
                    [self startCompletionDelayTimer];
                    break;
            }
        }
        [self didChangeValueForKey:@"currentState"];
    }
}

這個set方法就是這個類最核心的方法了听皿。它的作用如下:

  • 這里根據(jù)當前狀態(tài)熟呛,是否需要開始執(zhí)行一個延遲開始或者延遲完成,又或者是否需要取消這兩個延遲尉姨。
  • 還判斷了庵朝,是否需要去轉(zhuǎn)狀態(tài)欄的菊花,調(diào)用了setNetworkActivityIndicatorVisible:方法:
 - (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
    if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
        [self willChangeValueForKey:@"networkActivityIndicatorVisible"];
        @synchronized(self) {
             _networkActivityIndicatorVisible = networkActivityIndicatorVisible;
        }
        [self didChangeValueForKey:@"networkActivityIndicatorVisible"];
        
        //支持自定義的Block,去自己控制小菊花
        if (self.networkActivityActionBlock) {
            self.networkActivityActionBlock(networkActivityIndicatorVisible);
        } else {
            //否則默認AF根據(jù)該Bool九府,去控制狀態(tài)欄小菊花是否顯示
            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
        }
    }
}
  • 這個方法就是用來控制菊花是否轉(zhuǎn)椎瘟。并且支持一個自定義的Block,我們可以自己去拿到這個菊花是否應(yīng)該轉(zhuǎn)的狀態(tài)值,去做一些自定義的處理侄旬。
  • 如果我們沒有實現(xiàn)這個Block肺蔚,則調(diào)用:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];

去轉(zhuǎn)菊花。

回到state的set方法中儡羔,我們除了控制菊花去轉(zhuǎn)宣羊,還調(diào)用了以下4個方法:

//開始任務(wù)到結(jié)束的時間,默認為1秒笔链,如果1秒就結(jié)束段只,那么不轉(zhuǎn)菊花,延遲去開始轉(zhuǎn)
- (void)startActivationDelayTimer {
    //只執(zhí)行一次
    self.activationDelayTimer = [NSTimer
                                 timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
    //添加到主線程runloop去觸發(fā)
    [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}

//完成任務(wù)到下一個任務(wù)開始鉴扫,默認為0.17秒赞枕,如果0.17秒就開始下一個,那么不停  延遲去結(jié)束菊花轉(zhuǎn)
- (void)startCompletionDelayTimer {
    //先取消之前的
    [self.completionDelayTimer invalidate];
    //延遲執(zhí)行讓菊花不在轉(zhuǎn)
    self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
    [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}

- (void)cancelActivationDelayTimer {
    [self.activationDelayTimer invalidate];
}

- (void)cancelCompletionDelayTimer {
    [self.completionDelayTimer invalidate];
}

這4個方法分別是開始延遲執(zhí)行一個方法坪创,和結(jié)束的時候延遲執(zhí)行一個方法炕婶,和對應(yīng)這兩個方法的取消。其作用莱预,注釋應(yīng)該很容易理解柠掂。
我們繼續(xù)往下看,這兩個延遲調(diào)用的到底是什么:

- (void)activationDelayTimerFired {
    //活躍狀態(tài)依沮,即活躍數(shù)大于1才轉(zhuǎn)
    if (self.networkActivityOccurring) {
        [self setCurrentState:AFNetworkActivityManagerStateActive];
    } else {
        [self setCurrentState:AFNetworkActivityManagerStateNotActive];
    }
}
- (void)completionDelayTimerFired {
    [self setCurrentState:AFNetworkActivityManagerStateNotActive];
}

一個開始涯贞,一個完成調(diào)用,都設(shè)置了不同的currentState的值危喉,又回到之前stateset方法中了宋渔。

至此這個AFNetworkActivityIndicatorManager類就講完了,代碼還是相當簡單明了的辜限。

分割圖.png
2.UIImageView+AFNetworking

接下來我們來講一個我們經(jīng)常用的方法皇拣,這個方法的實現(xiàn)類是:UIImageView+AFNetworking.h
這是個類目薄嫡,并且給UIImageView擴展了4個方法:

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

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
      placeholderImage:(nullable UIImage *)placeholderImage
               success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
               failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
- (void)cancelImageDownloadTask;
  • 前兩個想必不用我說了氧急,沒有誰沒用過吧...就是給一個UIImageView去異步的請求一張圖片,并且可以設(shè)置一張占位圖毫深。
  • 第3個方法設(shè)置一張圖吩坝,并且可以拿到成功和失敗的回調(diào)。
  • 第4個方法哑蔫,可以取消當前的圖片設(shè)置請求钾恢。

無論SDWebImage,還是YYKit,或者AF手素,都實現(xiàn)了這么個類目。
AF關(guān)于這個類目UIImageView+AFNetworking的實現(xiàn)瘩蚪,依賴于這么兩個類:AFImageDownloaderAFAutoPurgingImageCache稿黍。
當然AFImageDownloader中疹瘦,關(guān)于圖片數(shù)據(jù)請求的部分,還是使用AFURLSessionManager來實現(xiàn)的巡球。

接下來我們就來看看AFImageDownloader:

先看看初始化方法:

//該類為單例
+ (instancetype)defaultInstance {
    static AFImageDownloader *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
- (instancetype)init {
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    //TODO set the default HTTP headers

    configuration.HTTPShouldSetCookies = YES;
    configuration.HTTPShouldUsePipelining = NO;

    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    //是否允許蜂窩網(wǎng)絡(luò)言沐,手機網(wǎng)
    configuration.allowsCellularAccess = YES;
    //默認超時
    configuration.timeoutIntervalForRequest = 60.0;
    //設(shè)置的圖片緩存對象
    configuration.URLCache = [AFImageDownloader defaultURLCache];

    return configuration;
}

該類為單例,上述方法中酣栈,創(chuàng)建了一個sessionManager,這個sessionManager將用于我們之后的網(wǎng)絡(luò)請求险胰。從這里我們可以看到,這個類的網(wǎng)絡(luò)請求都是基于之前AF自己封裝的AFHTTPSessionManager矿筝。

  • 在這里初始化了一系列的對象起便,需要講一下的是AFImageDownloadPrioritizationFIFO,這個一個枚舉值:
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
    //先進先出
    AFImageDownloadPrioritizationFIFO,
    //后進先出
    AFImageDownloadPrioritizationLIFO
};

這個枚舉值代表著窖维,一堆圖片下載榆综,執(zhí)行任務(wù)的順序。

  • 還有一個AFAutoPurgingImageCache的創(chuàng)建铸史,這個類是AF做圖片緩存用的鼻疮。這里我們暫時就這么理解它,講完當前類琳轿,我們再來補充它判沟。
  • 除此之外,我們還看到一個cache:
configuration.URLCache = [AFImageDownloader defaultURLCache];
//設(shè)置一個系統(tǒng)緩存崭篡,內(nèi)存緩存為20M挪哄,磁盤緩存為150M,
//這個是系統(tǒng)級別維護的緩存媚送。
 + (NSURLCache *)defaultURLCache {
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

大家看到這可能迷惑了中燥,怎么這么多cache,那AF做圖片緩存到底用哪個呢塘偎?答案是AF自己控制的圖片緩存用AFAutoPurgingImageCache疗涉,而NSUrlRequest的緩存由它自己內(nèi)部根據(jù)策略去控制,用的是NSURLCache吟秩,不歸AF處理咱扣,只需在configuration中設(shè)置上即可。

  • 那么看到這有些小伙伴又要問了涵防,為什么不直接用NSURLCache闹伪,還要自定義一個AFAutoPurgingImageCache呢?原來是因為NSURLCache的諸多限制,例如只支持get請求等等偏瓤。而且因為是系統(tǒng)維護的杀怠,我們自己的可控度不強,并且如果需要做一些自定義的緩存處理厅克,無法實現(xiàn)赔退。
  • 更多關(guān)于NSURLCache的內(nèi)容,大家可以自行查閱证舟。

接著上面的方法調(diào)用到這個最終的初始化方法中:

- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(id <AFImageRequestCache>)imageCache {
    if (self = [super init]) {
        //持有
        self.sessionManager = sessionManager;
        //定義下載任務(wù)的順序硕旗,默認FIFO,先進先出-隊列模式女责,還有后進先出-棧模式
        self.downloadPrioritizaton = downloadPrioritization;
        //最大的下載數(shù)
        self.maximumActiveDownloads = maximumActiveDownloads;
        
        //自定義的cache
        self.imageCache = imageCache;

        //隊列中的任務(wù)漆枚,待執(zhí)行的
        self.queuedMergedTasks = [[NSMutableArray alloc] init];
        //合并的任務(wù),所有任務(wù)的字典
        self.mergedTasks = [[NSMutableDictionary alloc] init];
        //活躍的request數(shù)
        self.activeRequestCount = 0;

        //用UUID來拼接名字
        NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
        //創(chuàng)建一個串行的queue
        self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);

        name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
        //創(chuàng)建并行queue
        self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    }

    return self;
}

這邊初始化了一些屬性抵知,這些屬性跟著注釋看應(yīng)該很容易明白其作用墙基。主要需要注意的就是,這里創(chuàng)建了兩個queue:一個串行的請求queue辛藻,和一個并行的響應(yīng)queue碘橘。

  • 這個串行queue,是用來做內(nèi)部生成task等等一系列業(yè)務(wù)邏輯的。它保證了我們在這些邏輯處理中的線程安全問題(迷惑的接著往下看)。
  • 這個并行queue,被用來做網(wǎng)絡(luò)請求完成的數(shù)據(jù)回調(diào)笆制。

接下來我們來看看它的創(chuàng)建請求task的方法:

- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                        success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
                                                        failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
    return [self downloadImageForURLRequest:request withReceiptID:[NSUUID UUID] success:success failure:failure];
}

- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                  withReceiptID:(nonnull NSUUID *)receiptID
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
    //還是類似之前的,同步串行去做下載的事 生成一個task,這些事情都是在當前線程中串行同步做的纺蛆,所以不用擔心線程安全問題。
    __block NSURLSessionDataTask *task = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        //url字符串
        NSString *URLIdentifier = request.URL.absoluteString;
        if (URLIdentifier == nil) {
            if (failure) {
                //錯誤返回规揪,沒Url
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                dispatch_async(dispatch_get_main_queue(), ^{
                    failure(request, nil, error);
                });
            }
            return;
        }

        //如果這個任務(wù)已經(jīng)存在桥氏,則添加成功失敗Block,然后直接返回,即一個url用一個request,可以響應(yīng)好幾個block
        //從自己task字典中根據(jù)Url去取AFImageDownloaderMergedTask猛铅,里面有task id url等等信息
        AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
        if (existingMergedTask != nil) {
            //里面包含成功和失敗Block和UUid
            AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
            //添加handler
            [existingMergedTask addResponseHandler:handler];
            //給task賦值
            task = existingMergedTask.task;
            return;
        }

        //根據(jù)request的緩存策略字支,加載緩存
        switch (request.cachePolicy) {
            //這3種情況都會去加載緩存
            case NSURLRequestUseProtocolCachePolicy:
            case NSURLRequestReturnCacheDataElseLoad:
            case NSURLRequestReturnCacheDataDontLoad: {
                //從cache中根據(jù)request拿數(shù)據(jù)
                UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
                if (cachedImage != nil) {
                    if (success) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            success(request, nil, cachedImage);
                        });
                    }
                    return;
                }
                break;
            }
            default:
                break;
        }

        //走到這說明即沒有請求中的request,也沒有cache,開始請求
        NSUUID *mergedTaskIdentifier = [NSUUID UUID];
        //task
        NSURLSessionDataTask *createdTask;
        __weak __typeof__(self) weakSelf = self;
        
        //用sessionManager的去請求,注意奸忽,只是創(chuàng)建task,還是掛起狀態(tài)
        createdTask = [self.sessionManager
                       dataTaskWithRequest:request
                       completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                           
                           //在responseQueue中回調(diào)數(shù)據(jù),初始化為并行queue
                           dispatch_async(self.responseQueue, ^{
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               
                               //拿到當前的task
                               AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
                               
                               //如果之前的task數(shù)組中堕伪,有這個請求的任務(wù)task,則從數(shù)組中移除
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   //安全的移除栗菜,并返回當前被移除的AF task
                                   mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                   //請求錯誤
                                   if (error) {
                                       //去遍歷task所有響應(yīng)的處理
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           //主線程欠雌,調(diào)用失敗的Block
                                           if (handler.failureBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                               });
                                           }
                                       }
                                   } else {
                                       //成功根據(jù)request,往cache里添加
                                       [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
                                       //調(diào)用成功Block
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.successBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               //減少活躍的任務(wù)數(shù)
                               [strongSelf safelyDecrementActiveTaskCount];
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });
                       }];

        // 4) Store the response handler for use when the request completes
        //創(chuàng)建handler
        AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                   success:success
                                                                                                   failure:failure];
        //創(chuàng)建task
        AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                   initWithURLIdentifier:URLIdentifier
                                                   identifier:mergedTaskIdentifier
                                                   task:createdTask];
        //添加handler
        [mergedTask addResponseHandler:handler];
        //往當前任務(wù)字典里添加任務(wù)
        self.mergedTasks[URLIdentifier] = mergedTask;

        // 5) Either start the request or enqueue it depending on the current active request count
        //如果小于,則開始任務(wù)下載resume
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            [self startMergedTask:mergedTask];
        } else {
            
            [self enqueueMergedTask:mergedTask];
        }
        //拿到最終生成的task
        task = mergedTask.task;
    });
    if (task) {
        //創(chuàng)建一個AFImageDownloadReceipt并返回疙筹,里面就多一個receiptID富俄。
        return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
    } else {
        return nil;
    }
}

就這么一個非常非常長的方法禁炒,這個方法執(zhí)行的內(nèi)容都是在我們之前創(chuàng)建的串行queue中,同步的執(zhí)行的霍比,這是因為這個方法絕大多數(shù)的操作都是需要線程安全的幕袱。可以對著源碼和注釋來看桂塞,我們在這講下它做了什么:

  1. 首先做了一個url的判斷凹蜂,如果為空則返回失敗Block。
  2. 判斷這個需要請求的url阁危,是不是已經(jīng)被生成的task中,如果是的話汰瘫,則多添加一個回調(diào)處理就可以狂打。回調(diào)處理對象為AFImageDownloaderResponseHandler混弥。這個類非常簡單趴乡,總共就如下3個屬性:
@interface AFImageDownloaderResponseHandler : NSObject
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
@end
@implementation AFImageDownloaderResponseHandler
//初始化回調(diào)對象
 - (instancetype)initWithUUID:(NSUUID *)uuid
                     success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
                     failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
    if (self = [self init]) {
        self.uuid = uuid;
        self.successBlock = success;
        self.failureBlock = failure;
    }
    return self;
}

當這個task完成的時候,會調(diào)用我們添加的回調(diào)蝗拿。

  1. 關(guān)于AFImageDownloaderMergedTask晾捏,我們在這里都用的是這種類型的task,其實這個task也很簡單:
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
@implementation AFImageDownloaderMergedTask
 - (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task {
    if (self = [self init]) {
        self.URLIdentifier = URLIdentifier;
        self.task = task;
        self.identifier = identifier;
        self.responseHandlers = [[NSMutableArray alloc] init];
    }
    return self;
}
//添加任務(wù)完成回調(diào)
 - (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers addObject:handler];
}
//移除任務(wù)完成回調(diào)
 - (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers removeObject:handler];
}
@end

其實就是除了NSURLSessionDataTask哀托,多加了幾個參數(shù)惦辛,URLIdentifieridentifier都是用來標識這個task的,responseHandlers是用來存儲task完成后的回調(diào)的仓手,里面可以存一組胖齐,當任務(wù)完成時候,里面的回調(diào)都會被調(diào)用嗽冒。

  1. 接著去根據(jù)緩存策略呀伙,去加載緩存,如果有緩存添坊,從self.imageCache中返回緩存剿另,否則繼續(xù)往下走。
  2. 走到這說明沒相同url的task贬蛙,也沒有cache雨女,那么就開始一個新的task,調(diào)用的是AFUrlSessionManager里的請求方法生成了一個task(這里我們就不贅述了速客,可以看之前的樓主之前的文章)戚篙。然后做了請求完成的處理。注意溺职,這里處理實在我們一開始初始化的并行queue:self.responseQueue中的岔擂,這里的響應(yīng)處理是多線程并發(fā)進行的位喂。
    1)完成,則調(diào)用如下方法把這個task從全局字典中移除:
 //移除task相關(guān)乱灵,用同步串行的形式塑崖,防止移除中出現(xiàn)重復(fù)移除一系列問題
  - (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
    __block AFImageDownloaderMergedTask *mergedTask = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
    });
    return mergedTask;
}

2)去循環(huán)這個task的responseHandlers,調(diào)用它的成功或者失敗的回調(diào)痛倚。
3)并且調(diào)用下面兩個方法规婆,去減少正在請求的任務(wù)數(shù),和開啟下一個任務(wù):

//減少活躍的任務(wù)數(shù)
 - (void)safelyDecrementActiveTaskCount {
    //回到串行queue去-
    dispatch_sync(self.synchronizationQueue, ^{
        if (self.activeRequestCount > 0) {
            self.activeRequestCount -= 1;
        }
    });
}
//如果可以蝉稳,則開啟下一個任務(wù)
 - (void)safelyStartNextTaskIfNecessary {
    //回到串行queue
    dispatch_sync(self.synchronizationQueue, ^{
        //先判斷并行數(shù)限制
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            while (self.queuedMergedTasks.count > 0) {
                //獲取數(shù)組中第一個task
                AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
                //如果狀態(tài)是掛起狀態(tài)
                if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
                    [self startMergedTask:mergedTask];
                    break;
                }
            }
        }
    });
}

這里需要注意的是抒蚜,跟我們本類的一些數(shù)據(jù)相關(guān)的操作,都是在我們一開始的串行queue中同步進行的耘戚。
4)除此之外嗡髓,如果成功,還把成功請求到的數(shù)據(jù)收津,加到AF自定義的cache中:

//成功根據(jù)request,往cache里添加
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
  1. NSUUID生成的唯一標識饿这,去生成AFImageDownloaderResponseHandler,然后生成一個AFImageDownloaderMergedTask撞秋,把之前第5步生成的createdTask和回調(diào)都綁定給這個AF自定義可合并回調(diào)的task长捧,然后這個task加到全局的task映射字典中,key為url:
self.mergedTasks[URLIdentifier] = mergedTask;
  1. 判斷當前正在下載的任務(wù)是否超過最大并行數(shù)吻贿,如果沒有則開始下載串结,否則先加到等待的數(shù)組中去:
//如果小于最大并行數(shù),則開始任務(wù)下載resume
if ([self isActiveRequestCountBelowMaximumLimit]) {
    [self startMergedTask:mergedTask];
} else {
    
    [self enqueueMergedTask:mergedTask];
}
//判斷并行數(shù)限制
 - (BOOL)isActiveRequestCountBelowMaximumLimit {
    return self.activeRequestCount < self.maximumActiveDownloads;
}
//開始下載
 - (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    [mergedTask.task resume];
    //任務(wù)活躍數(shù)+1
    ++self.activeRequestCount;
}
//把任務(wù)先加到數(shù)組里
 - (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    switch (self.downloadPrioritizaton) {
            //先進先出
        case AFImageDownloadPrioritizationFIFO:
            [self.queuedMergedTasks addObject:mergedTask];
            break;
            //后進先出
        case AFImageDownloadPrioritizationLIFO:
            [self.queuedMergedTasks insertObject:mergedTask atIndex:0];
            break;
    }
}
  • 先判斷并行數(shù)限制廓八,如果小于最大限制奉芦,則開始下載,把當前活躍的request數(shù)量+1剧蹂。
  • 如果暫時不能下載声功,被加到等待下載的數(shù)組中去的話,會根據(jù)我們一開始設(shè)置的下載策略宠叼,是先進先出先巴,還是后進先出,去插入這個下載任務(wù)冒冬。
  1. 最后判斷這個mergeTask是否為空伸蚯。不為空,我們生成了一個AFImageDownloadReceipt简烤,綁定了一個UUID剂邮。否則為空返回nil:
if (task) {
    //創(chuàng)建一個AFImageDownloadReceipt并返回,里面就多一個receiptID横侦。
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

這個AFImageDownloadReceipt僅僅是多封裝了一個UUID:

@interface AFImageDownloadReceipt : NSObject
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSUUID *receiptID;
@end
@implementation AFImageDownloadReceipt
 - (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task {
    if (self = [self init]) {
        self.receiptID = receiptID;
        self.task = task;
    }
    return self;
}

這么封裝是為了標識每一個task挥萌,我們后面可以根據(jù)這個AFImageDownloadReceipt來對task做取消操作绰姻。

這個AFImageDownloader中最核心的方法基本就講完了,還剩下一些方法沒講引瀑,像前面講到的task的取消的方法:

//根據(jù)AFImageDownloadReceipt來取消任務(wù)狂芋,即對應(yīng)一個響應(yīng)回調(diào)。
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    dispatch_sync(self.synchronizationQueue, ^{
        //拿到url
        NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
        //根據(jù)url拿到task
        AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
        
        //快速遍歷查找某個下標憨栽,如果返回YES帜矾,則index為當前下標
        NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
            
            return handler.uuid == imageDownloadReceipt.receiptID;
        }];

        if (index != NSNotFound) {
            //移除響應(yīng)處理
            AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
            [mergedTask removeResponseHandler:handler];
            NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
            //并調(diào)用失敗block,原因為取消
            if (handler.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
                });
            }
        }
        
        //如果任務(wù)里的響應(yīng)回調(diào)為空或者狀態(tài)為掛起屑柔,則取消task,并且從字典中移除
        if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
            [mergedTask.task cancel];
            [self removeMergedTaskWithURLIdentifier:URLIdentifier];
        }
    });
}
//根據(jù)URLIdentifier移除task
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
    AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
    [self.mergedTasks removeObjectForKey:URLIdentifier];
    return mergedTask;
}

方法比較簡單屡萤,大家自己看看就好。至此```AFImageDownloader``這個類講完了掸宛。如果大家看的感覺比較繞灭衷,沒關(guān)系,等到最后我們一起來總結(jié)一下旁涤,捋一捋。

分割圖.png

我們之前講到AFAutoPurgingImageCache這個類略過去了迫像,現(xiàn)在我們就來補充一下這個類的相關(guān)內(nèi)容:
首先來講講這個類的作用劈愚,它是AF自定義用來做圖片緩存的。我們來看看它的初始化方法:

- (instancetype)init {
    //默認為內(nèi)存100M闻妓,后者為緩存溢出后保留的內(nèi)存
    return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}

- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
    if (self = [super init]) {
        //內(nèi)存大小
        self.memoryCapacity = memoryCapacity;
        self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
        //cache的字典
        self.cachedImages = [[NSMutableDictionary alloc] init];

        NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
        //并行的queue
        self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);

        //添加通知菌羽,收到內(nèi)存警告的通知
        [[NSNotificationCenter defaultCenter]
         addObserver:self
         selector:@selector(removeAllImages)
         name:UIApplicationDidReceiveMemoryWarningNotification
         object:nil];

    }
    return self;
}

初始化方法很簡單,總結(jié)一下:

  1. 聲明了一個默認的內(nèi)存緩存大小100M由缆,還有一個意思是如果超出100M之后注祖,我們?nèi)デ宄彺妫藭r仍要保留的緩存大小60M均唉。(如果還是不理解是晨,可以看后文,源碼中會講到)
  2. 創(chuàng)建了一個并行queue舔箭,這個并行queue罩缴,這個類除了初始化以外,所有的方法都是在這個并行queue中調(diào)用的层扶。
  3. 創(chuàng)建了一個cache字典箫章,我們所有的緩存數(shù)據(jù),都被保存在這個字典中镜会,key為url檬寂,value為AFCachedImage
    關(guān)于這個AFCachedImage戳表,其實就是Image之外封裝了幾個關(guān)于這個緩存的參數(shù)桶至,如下:
@interface AFCachedImage : NSObject
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *identifier;  //url標識
@property (nonatomic, assign) UInt64 totalBytes;   //總大小
@property (nonatomic, strong) NSDate *lastAccessDate;  //上次獲取時間
@property (nonatomic, assign) UInt64 currentMemoryUsage; //這個參數(shù)沒被用到過
@end
@implementation AFCachedImage
//初始化
 -(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
    if (self = [self init]) {
        self.image = image;
        self.identifier = identifier;

        CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
        CGFloat bytesPerPixel = 4.0;
        CGFloat bytesPerSize = imageSize.width * imageSize.height;
        self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
        self.lastAccessDate = [NSDate date];
    }
    return self;
}
//上次獲取緩存的時間
 - (UIImage*)accessImage {
    self.lastAccessDate = [NSDate date];
    return self.image;
}
  1. 添加了一個通知昼伴,監(jiān)聽內(nèi)存警告,當發(fā)成內(nèi)存警告塞茅,調(diào)用該方法亩码,移除所有的緩存,并且把當前緩存數(shù)置為0:
//移除所有圖片
 - (BOOL)removeAllImages {
    __block BOOL removed = NO;
    dispatch_barrier_sync(self.synchronizationQueue, ^{
        if (self.cachedImages.count > 0) {
            [self.cachedImages removeAllObjects];
            self.currentMemoryUsage = 0;
            removed = YES;
        }
    });
    return removed;
}

注意這個類大量的使用了dispatch_barrier_syncdispatch_barrier_async野瘦,小伙伴們?nèi)绻麑@兩個方法有任何疑惑描沟,可以看看這篇文章:dispatch_barrier_async與dispatch_barrier_sync異同
1)這里我們可以看到使用了dispatch_barrier_sync鞭光,這里沒有用鎖吏廉,但是因為使用了dispatch_barrier_sync,不僅同步了synchronizationQueue隊列惰许,而且阻塞了當前線程席覆,所以保證了里面執(zhí)行代碼的線程安全問題。
2)在這里其實使用鎖也可以汹买,但是AF在這的處理卻是使用同步的機制來保證線程安全佩伤,或許這跟圖片的加載緩存的使用場景,高頻次有關(guān)系晦毙,在這里使用sync生巡,并不需要在去開辟新的線程,浪費性能见妒,只需要在原有線程孤荣,提交到synchronizationQueue隊列中,阻塞的執(zhí)行即可须揣。這樣省去大量的開辟線程與使用鎖帶來的性能消耗盐股。(當然這僅僅是我的一個猜測,有不同意見的朋友歡迎討論~)

  • 在這里用了dispatch_barrier_sync耻卡,因為synchronizationQueue是個并行queue疯汁,所以在這里不會出現(xiàn)死鎖的問題。
  • 關(guān)于保證線程安全的同時劲赠,同步還是異步涛目,與性能方面的考量,可以參考這篇文章:Objc的底層并發(fā)API凛澎。

接著我們來看看這個類最核心的一個方法:

//添加image到cache里
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
   
    //用dispatch_barrier_async霹肝,來同步這個并行隊列
    dispatch_barrier_async(self.synchronizationQueue, ^{
        //生成cache對象
        AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
        
        //去之前cache的字典里取
        AFCachedImage *previousCachedImage = self.cachedImages[identifier];
        //如果有被緩存過
        if (previousCachedImage != nil) {
            //當前已經(jīng)使用的內(nèi)存大小減去圖片的大小
            self.currentMemoryUsage -= previousCachedImage.totalBytes;
        }
        //把新cache的image加上去
        self.cachedImages[identifier] = cacheImage;
        //加上內(nèi)存大小
        self.currentMemoryUsage += cacheImage.totalBytes;
    });

    //做緩存溢出的清除,清除的是早期的緩存
    dispatch_barrier_async(self.synchronizationQueue, ^{
        //如果使用的內(nèi)存大于我們設(shè)置的內(nèi)存容量
        if (self.currentMemoryUsage > self.memoryCapacity) {
            //拿到使用內(nèi)存 - 被清空后首選內(nèi)存 =  需要被清除的內(nèi)存
            UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
            //拿到所有緩存的數(shù)據(jù)
            NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
            
            //根據(jù)lastAccessDate排序 升序塑煎,越晚的越后面
            NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
                                                                           ascending:YES];
            
            [sortedImages sortUsingDescriptors:@[sortDescriptor]];

            UInt64 bytesPurged = 0;
            //移除早期的cache bytesToPurge大小
            for (AFCachedImage *cachedImage in sortedImages) {
                [self.cachedImages removeObjectForKey:cachedImage.identifier];
                bytesPurged += cachedImage.totalBytes;
                if (bytesPurged >= bytesToPurge) {
                    break ;
                }
            }
            //減去被清掉的內(nèi)存
            self.currentMemoryUsage -= bytesPurged;
        }
    });
}

看注釋應(yīng)該很容易明白沫换,這個方法做了兩件事:

  1. 設(shè)置緩存到字典里,并且把對應(yīng)的緩存大小設(shè)置到當前已緩存的數(shù)量屬性中。
  2. 判斷是緩存超出了我們設(shè)置的最大緩存100M讯赏,如果是的話垮兑,則清除掉部分早時間的緩存,清除到緩存小于我們溢出后保留的內(nèi)存60M以內(nèi)漱挎。

當然在這里更需要說一說的是dispatch_barrier_async系枪,這里整個類都沒有使用dispatch_async,所以不存在是為了做一個柵欄磕谅,來同步上下文的線程私爷。其實它在本類中的作用很簡單,就是一個串行執(zhí)行膊夹。

  • 講到這衬浑,小伙伴們又疑惑了,既然就是只是為了串行放刨,那為什么我們不用一個串行queue就得了工秩?非得用dispatch_barrier_async干嘛?其實小伙伴要是看的仔細进统,就明白了助币,上文我們說過,我們要用dispatch_barrier_sync來保證線程安全螟碎。如果我們使用串行queue,那么線程是極其容易死鎖的奠支。

還有剩下的幾個方法:

//根據(jù)id獲取圖片
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
    __block UIImage *image = nil;
    //用同步的方式獲取,防止線程安全問題
    dispatch_sync(self.synchronizationQueue, ^{
        AFCachedImage *cachedImage = self.cachedImages[identifier];
        //并且刷新獲取的時間
        image = [cachedImage accessImage];
    });
    return image;
}

//根據(jù)request和additionalIdentifier添加cache
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
    [self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}

//根據(jù)request和additionalIdentifier移除圖片
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
    return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
//根據(jù)request和additionalIdentifier獲取圖片

- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
    return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}

//生成id的方式為Url字符串+additionalIdentifier
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
    NSString *key = request.URL.absoluteString;
    if (additionalIdentifier != nil) {
        key = [key stringByAppendingString:additionalIdentifier];
    }
    return key;
}

這幾個方法都很簡單抚芦,大家自己看看就好了,就不贅述了迈螟。至此AFAutoPurgingImageCache也講完了叉抡,我們還是等到最后再來總結(jié)。

分割圖.png

我們繞了一大圈答毫,總算回到了UIImageView+AFNetworking這個類褥民,現(xiàn)在圖片下載的方法,和緩存的方法都有了洗搂,實現(xiàn)這個類也是水到渠成的事了消返。

我們來看下面我們絕大多數(shù)人很熟悉的方法,看看它的實現(xiàn):

- (void)setImageWithURL:(NSURL *)url {
    [self setImageWithURL:url placeholderImage:nil];
}

- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage
{
    //設(shè)置head耘拇,可接受類型為image
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}

上述方法按順序往下調(diào)用撵颊,第二個方法給head的Accept類型設(shè)置為Image。接著調(diào)用到第三個方法惫叛,也是這個類目唯一一個重要的方法:

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
    //url為空倡勇,則取消
    if ([urlRequest URL] == nil) {
        //取消task
        [self cancelImageDownloadTask];
        //設(shè)置為占位圖
        self.image = placeholderImage;
        return;
    }
    
    //看看設(shè)置的當前的回調(diào)的request和需要請求的request是不是為同一個,是的話為重復(fù)調(diào)用嘉涌,直接返回
    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }
    
    //開始請求前妻熊,先取消之前的task,即解綁回調(diào)
    [self cancelImageDownloadTask];

    //拿到downloader
    AFImageDownloader *downloader = [[self class] sharedImageDownloader];
    //拿到cache
    id <AFImageRequestCache> imageCache = downloader.imageCache;

    //Use the image from the image cache if it exists
    UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
    //去獲取cachedImage
    if (cachedImage) {
        //有的話直接設(shè)置夸浅,并且置空回調(diào)
        if (success) {
            success(urlRequest, nil, cachedImage);
        } else {
            self.image = cachedImage;
        }
        [self clearActiveDownloadInformation];
    } else {
        //無緩存,如果有占位圖扔役,先設(shè)置
        if (placeholderImage) {
            self.image = placeholderImage;
        }

        __weak __typeof(self)weakSelf = self;
        NSUUID *downloadID = [NSUUID UUID];
        AFImageDownloadReceipt *receipt;
        //去下載帆喇,并得到一個receipt,可以用來取消回調(diào)
        receipt = [downloader
                   downloadImageForURLRequest:urlRequest
                   withReceiptID:downloadID
                   success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                       //判斷receiptID和downloadID是否相同 成功回調(diào)亿胸,設(shè)置圖片
                       if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                           if (success) {
                               success(request, response, responseObject);
                           } else if(responseObject) {
                               strongSelf.image = responseObject;
                           }
                           //置空回調(diào)
                           [strongSelf clearActiveDownloadInformation];
                       }

                   }
                   failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                       //失敗有failuerBlock就回調(diào)坯钦,
                        if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                            if (failure) {
                                failure(request, response, error);
                            }
                            //置空回調(diào)對象
                            [strongSelf clearActiveDownloadInformation];
                        }
                   }];
        //賦值
        self.af_activeImageDownloadReceipt = receipt;
    }
}

這個方法,細節(jié)的地方可以關(guān)注注釋损敷,這里總結(jié)一下做了什么:
1)去判斷url是否為空葫笼,如果為空則取消task,調(diào)用如下方法:

//取消task
- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        //取消事件回調(diào)響應(yīng)
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        //置空
        [self clearActiveDownloadInformation];
     }
}
//置空
- (void)clearActiveDownloadInformation {
    self.af_activeImageDownloadReceipt = nil;
}

  • 這里注意cancelImageDownloadTask中,調(diào)用了self.af_activeImageDownloadReceipt這么一個屬性拗馒,看看定義的地方:
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
//綁定屬性 AFImageDownloadReceipt路星,就是一個事件響應(yīng)的接受對象,包含一個task诱桂,一個uuid
 - (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
//set
 - (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

我們現(xiàn)在是給UIImageView添加的一個類目洋丐,所以我們無法直接添加屬性,而是使用的是runtime的方式來生成set和get方法生成了一個AFImageDownloadReceipt類型的屬性挥等∮丫看過上文應(yīng)該知道這個對象里面就一個task和一個UUID。這個屬性就是我們這次下載任務(wù)相關(guān)聯(lián)的信息肝劲。

2)然后做了一系列判斷迁客,見注釋。
3)然后生成了一個我們之前分析過得AFImageDownloader辞槐,然后去獲取緩存掷漱,如果有緩存,則直接讀緩存榄檬。還記得AFImageDownloader里也有一個讀緩存的方法么卜范?那個是和cachePolicy相關(guān)的,而這個是有緩存的話直接讀取鹿榜。不明白的可以回過頭去看看海雪。
4)走到這說明沒緩存了,然后就去用AFImageDownloader舱殿,我們之前講過的方法奥裸,去請求圖片。完成后沪袭,則調(diào)用成功或者失敗的回調(diào)刺彩,并且置空屬性self.af_activeImageDownloadReceipt,成功則設(shè)置圖片。

除此之外還有一個取消這次任務(wù)的方法:

//取消task
- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        //取消事件回調(diào)響應(yīng)
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        //置空
        [self clearActiveDownloadInformation];
     }
}

其實也是去調(diào)用我們之前講過的AFImageDownloader的取消方法创倔。

這個類總共就這么幾行代碼嗡害,就完成了我們幾乎沒有人不用的,設(shè)置ImageView圖片的方法畦攘。當然真正的難點在于AFImageDownloaderAFAutoPurgingImageCache霸妹。

接下來我們來總結(jié)一下整個請求圖片,緩存知押,然后設(shè)置圖片的流程:
  • 調(diào)用- (void)setImageWithURL:(NSURL *)url;時叹螟,我們生成
    AFImageDownloader單例,并替我們請求數(shù)據(jù)台盯。
  • AFImageDownloader會生成一個AFAutoPurgingImageCache替我們緩存生成的數(shù)據(jù)罢绽。當然我們設(shè)置的時候,給sessionconfiguration設(shè)置了一個系統(tǒng)級別的緩存NSUrlCache,這兩者是互相獨立工作的静盅,互不影響的良价。
  • 然后AFImageDownloader,就實現(xiàn)下載和協(xié)調(diào)AFAutoPurgingImageCache去緩存蒿叠,還有一些取消下載的方法明垢。然后通過回調(diào)把數(shù)據(jù)給到我們的類目UIImageView+AFNetworking,如果成功獲取數(shù)據(jù),則由類目設(shè)置上圖片市咽,整個流程結(jié)束痊银。

經(jīng)過這三個文件:
UIImageView+AFNetworkingAFImageDownloader施绎、AFAutoPurgingImageCache溯革,至此整個設(shè)置網(wǎng)絡(luò)圖片的方法結(jié)束了。

寫在最后:
  • 對于UIKit的總結(jié)谷醉,我們就到此為止了鬓照,其它部分的擴展,小伙伴們可以自行閱讀孤紧,都很簡單,基本上每個類200行左右的代碼拒秘。核心功能基本上都是圍繞AFURLSessionManager實現(xiàn)的号显。

  • 本來想本篇放在三里面完結(jié),想想還是覺得自己...too young too simple...
    但是下一篇應(yīng)該是一個結(jié)束了躺酒,我們會講講AF2.x押蚤,然后詳細總結(jié)一下AF存在的意義。大家任何有疑問或者不同意見的羹应,歡迎評論揽碘,樓主會一一回復(fù)的。求關(guān)注,求贊??雳刺。感謝~~

后續(xù)文章:

AFNetworking到底做了什么劫灶?(終)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掖桦,隨后出現(xiàn)的幾起案子本昏,更是在濱河造成了極大的恐慌,老刑警劉巖枪汪,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涌穆,死亡現(xiàn)場離奇詭異,居然都是意外死亡雀久,警方通過查閱死者的電腦和手機宿稀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赖捌,“玉大人祝沸,你說我怎么就攤上這事⊙舱海” “怎么了奋隶?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長悦荒。 經(jīng)常有香客問我唯欣,道長,這世上最難降的妖魔是什么搬味? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任境氢,我火速辦了婚禮,結(jié)果婚禮上碰纬,老公的妹妹穿的比我還像新娘萍聊。我一直安慰自己,他們只是感情好悦析,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布寿桨。 她就那樣靜靜地躺著,像睡著了一般强戴。 火紅的嫁衣襯著肌膚如雪亭螟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天骑歹,我揣著相機與錄音预烙,去河邊找鬼。 笑死道媚,一個胖子當著我的面吹牛扁掸,可吹牛的內(nèi)容都是我干的翘县。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼谴分,長吁一口氣:“原來是場噩夢啊……” “哼锈麸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狸剃,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤掐隐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钞馁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虑省,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年僧凰,在試婚紗的時候發(fā)現(xiàn)自己被綠了探颈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡训措,死狀恐怖伪节,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绩鸣,我是刑警寧澤怀大,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站呀闻,受9級特大地震影響化借,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捡多,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一蓖康、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垒手,春花似錦蒜焊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榜掌,卻和暖如春优妙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唐责。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘾带,地道東北人鼠哥。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓熟菲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親朴恳。 傳聞我的和親對象是個殘疾皇子抄罕,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容