原始碼閱讀:AFNetworking(九)——AFImageDownloader

堯少羽發表於2018-03-11

該文章閱讀的AFNetworking的版本為3.2.0。

該類用於圖片的下載

1.列舉

typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
    AFImageDownloadPrioritizationFIFO,
    AFImageDownloadPrioritizationLIFO
};
複製程式碼

該列舉定義了圖片下載的優先順序:

AFImageDownloadPrioritizationFIFO表示下載圖片時按照先進先出的原則

AFImageDownloadPrioritizationLIFO表示下載圖片時按照後進先出的原則

2.公共私有類

2.1.AFImageDownloadReceipt類

這個類是一個公共類,它是對一個task的封裝,並用UUID作為識別符號。

2.1.1.介面

/**
 AFImageDownloader建立的task
*/
@property (nonatomic, strong) NSURLSessionDataTask *task;

/**
 識別符號
 */
@property (nonatomic, strong) NSUUID *receiptID;
複製程式碼

2.1.2.實現

實現非常簡單,只有一個自定義的初始化方法,將task和識別符號進行儲存。

- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task {
    if (self = [self init]) {
        self.receiptID = receiptID;
        self.task = task;
    }
    return self;
}
複製程式碼

2.2.AFImageDownloaderResponseHandler類

這個類是封裝task的回撥block,並用UUID作為識別符號

2.2.1.介面

/**
 手機的UUID
 */
@property (nonatomic, strong) NSUUID *uuid;

/**
 成功回撥block
 */
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);

/**
 失敗回撥block
 */
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
複製程式碼

2.2.1.實現

它的實現同樣也是很簡單,一個自定義初始化方法,一個列印資料方法

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

- (NSString *)description {
    // 定製列印資料
    return [NSString stringWithFormat: @"<AFImageDownloaderResponseHandler>UUID: %@", [self.uuid UUIDString]];
}
複製程式碼

2.3.AFImageDownloaderMergedTask類

這個類是將同樣的任務進項合併,但保留回撥block,同樣是以UUID作為識別符號

2.3.1.介面

/**
 圖片連結識別符號
 */
@property (nonatomic, strong) NSString *URLIdentifier;

/**
 手機的UUID
 */
@property (nonatomic, strong) NSUUID *identifier;

/**
 下載任務
 */
@property (nonatomic, strong) NSURLSessionDataTask *task;

/**
 響應回撥陣列
 */
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
複製程式碼

2.3.2.實現

/**
 自定義初始化方法
 */
- (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;
}

/**
 新增響應回撥
 */
- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers addObject:handler];
}

/**
 刪除響應回撥
 */
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers removeObject:handler];
}
複製程式碼

3.AFImageDownloader類的介面

3.1.屬性

/**
 圖片快取物件,預設是AFAutoPurgingImageCache物件
 */
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;

/**
 用來下載圖片的網路會話管理物件,預設是AFImageResponseSerializer物件
 */
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

/**
 下載任務佇列的優先順序,預設是先進先出
 */
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;
複製程式碼

3.2.方法

/**
 獲取圖片下載器單例物件
 */
+ (instancetype)defaultInstance;

/**
 獲取預設的URL快取物件
 */
+ (NSURLCache *)defaultURLCache;

/**
 獲取預設的網路會話配置物件
 */
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration;

/**
 預設的初始化方法
 */
- (instancetype)init;

/**
 指定網路會話配置物件的初始化方法
 */
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;

/**
 以指定網路會話管理者、下載優先順序、最大下載數量和圖片快取物件的初始化方法
 */
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(nullable id <AFImageRequestCache>)imageCache;

/**
 以指定請求建立一個圖片下載的task。但是,如果相同的任務已經在佇列中或者正在下載,就不會建立task,而是直接把成功和失敗回撥與原task相關聯
 */
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;

/**
 以指定請求和識別符號建立一個圖片下載的task。但是,如果相同的任務已經在佇列中或者正在下載,就不會建立task,而是直接把成功和失敗回撥與原task相關聯。
 */
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                 withReceiptID:(NSUUID *)receiptID
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;

/**
 取消下載任務,如果任務在佇列中,任務會被取消;如果任務正在執行或者已完成,成功和失敗回撥會被刪除。
 */
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;
複製程式碼

4.AFImageDownloader類的類擴充套件

/**
 序列佇列
 */
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;

/**
 響應佇列
 */
@property (nonatomic, strong) dispatch_queue_t responseQueue;

/**
 最大下載數
 */
@property (nonatomic, assign) NSInteger maximumActiveDownloads;

/**
 當前活動請求數
 */
@property (nonatomic, assign) NSInteger activeRequestCount;

/**
 佇列中的任務
 */
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;

/**
 儲存任務和其識別符號
 */
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
複製程式碼

5.AFImageDownloader類的實現

  • 公共方法的實現
+ (NSURLCache *)defaultURLCache {
    
    // 這個地方作者解釋說,在某些版本中自定義NSURLCache會導致崩潰。從下面的程式碼可以看出,當系統版本比iOS8.2小的時候用系統預設的快取方法,否則就是記憶體20M,硬碟150M的自定義快取容量
    if ([[[UIDevice currentDevice] systemVersion] compare:@"8.2" options:NSNumericSearch] == NSOrderedAscending) {
        return [NSURLCache sharedURLCache];
    }
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    // 例項化網路會話配置類物件並設定一些屬性
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    //TODO set the default HTTP headers

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

    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    configuration.allowsCellularAccess = YES;
    configuration.timeoutIntervalForRequest = 60.0;
    configuration.URLCache = [AFImageDownloader defaultURLCache];

    return configuration;
}

- (instancetype)init {
    // 例項化網路會話配置類物件並以之為引數呼叫下面的方法
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    return [self initWithSessionConfiguration:defaultConfiguration];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    // 利用引數例項化AFHTTPSessionManager物件,然後以之為引數呼叫下面的方法
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(id <AFImageRequestCache>)imageCache {
    if (self = [super init]) {
        // 用屬性記錄引數
        self.sessionManager = sessionManager;

        self.downloadPrioritizaton = downloadPrioritization;
        self.maximumActiveDownloads = maximumActiveDownloads;
        self.imageCache = imageCache;

        // 初始化屬性
        self.queuedMergedTasks = [[NSMutableArray alloc] init];
        self.mergedTasks = [[NSMutableDictionary alloc] init];
        self.activeRequestCount = 0;

        NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
        self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);

        name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
        self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    }

    return self;
}

+ (instancetype)defaultInstance {
    // 生成單例物件
    static AFImageDownloader *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                        success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
                                                        failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
    // 以識別符號為UUID呼叫下面的方法
    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 {
    // 生成臨時變數
    __block NSURLSessionDataTask *task = nil;
    // 同步序列佇列
    dispatch_sync(self.synchronizationQueue, ^{
        // 如果沒有圖片連線就返回並回撥錯誤資訊
        NSString *URLIdentifier = request.URL.absoluteString;
        if (URLIdentifier == nil) {
            if (failure) {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                dispatch_async(dispatch_get_main_queue(), ^{
                    failure(request, nil, error);
                });
            }
            return;
        }

        // 1) 如果已經傳送過這個請求,就直接把回撥放到陣列中等待回撥,task直接賦值,不再生成,然後返回
        AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
        if (existingMergedTask != nil) {
            AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
            [existingMergedTask addResponseHandler:handler];
            task = existingMergedTask.task;
            return;
        }

        // 2) 如果快取策略允許就從快取中載入影象
        switch (request.cachePolicy) {
            case NSURLRequestUseProtocolCachePolicy:
            case NSURLRequestReturnCacheDataElseLoad:
            case NSURLRequestReturnCacheDataDontLoad: {
                // 從圖片快取物件中獲取圖片
                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;
        }

        // 3) 建立請求並設定認證,驗證和響應序列化
        NSUUID *mergedTaskIdentifier = [NSUUID UUID];
        NSURLSessionDataTask *createdTask;
        __weak __typeof__(self) weakSelf = self;

        createdTask = [self.sessionManager
                       dataTaskWithRequest:request
                       uploadProgress:nil
                       downloadProgress:nil
                       completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                           dispatch_async(self.responseQueue, ^{
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               // 獲取到URLIdentifier對應的任務物件
                               AFImageDownloaderMergedTask *mergedTask = strongSelf.mergedTasks[URLIdentifier];
                               // 如果是同一個任務物件
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   // 就從任務佇列中刪除
                                   mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                   // 如果出錯就找出對應的錯誤回撥block進行回撥
                                   if (error) {
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.failureBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                               });
                                           }
                                       }
                                   // 如果不是同一個任務
                                   } else {
                                       // 把下載完成的圖片新增到快取中
                                       if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
                                           [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
                                       }
                                        
                                       // 找出對應的成功回撥block進行回撥
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.successBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               // 減少當前的任務數
                               [strongSelf safelyDecrementActiveTaskCount];
                               // 開始下一個任務
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });
                       }];

        // 4) 儲存響應處理回撥以在請求完成時使用
        // 根據識別符號、成功回撥block和失敗回撥block生成圖片下載響應物件
        AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                   success:success
                                                                                                   failure:failure];
        // 根據圖片連結、識別符號和任務生成圖片下載合併任務物件
        AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                   initWithURLIdentifier:URLIdentifier
                                                   identifier:mergedTaskIdentifier
                                                   task:createdTask];
        // 將生成的圖片下載響應物件新增到圖片下載合併任務物件中
        [mergedTask addResponseHandler:handler];
        // 根據圖片連結儲存其對應的圖片下載合併任務物件
        self.mergedTasks[URLIdentifier] = mergedTask;

        // 5) 根據當前的活動請求計數啟動請求或將其排入佇列
        // 如果沒達到請求數量上限,就啟動任務,否則就排隊
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            [self startMergedTask:mergedTask];
        } else {
            [self enqueueMergedTask:mergedTask];
        }

        task = mergedTask.task;
    });
    
    // 生成任務封裝類並返回
    if (task) {
        return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
    } else {
        return nil;
    }
}

- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    dispatch_sync(self.synchronizationQueue, ^{
        // 通過圖片封裝物件獲取到任務的連結
        NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
        // 通過連結獲取到任務
        AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
        // 通過識別符號獲取到響應物件在陣列中的索引
        NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
            return handler.uuid == imageDownloadReceipt.receiptID;
        }];

        // 如果找到了圖片封裝物件對應的響應物件
        if (index != NSNotFound) {
            // 通過索引獲取到響應物件
            AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
            // 從陣列中移除掉響應物件
            [mergedTask removeResponseHandler:handler];
            // 生成錯誤物件並回撥錯誤block
            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];
            if (handler.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
                });
            }
        }

        // 如果回撥物件為空,並且任務已經暫停。就取消任務,並移除掉對應的任務合併物件
        if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
            [mergedTask.task cancel];
            [self removeMergedTaskWithURLIdentifier:URLIdentifier];
        }
    });
}
複製程式碼
  • 私有方法
/**
 通過識別符號安全移除合併任務物件
 */
- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
    // 序列佇列同步執行移除合併任務物件
    __block AFImageDownloaderMergedTask *mergedTask = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
    });
    return mergedTask;
}

/**
 通過識別符號移除合併任務物件,這個方法只能在self.synchronizationQueue佇列中安全呼叫
 */
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
    // 通過識別符號從可變字典中獲取合併任務物件
    AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
    // 通過識別符號從可變字典中移除對應的合併任務物件
    [self.mergedTasks removeObjectForKey:URLIdentifier];
    return mergedTask;
}

/**
 安全的減少當前任務活動數量
 */
- (void)safelyDecrementActiveTaskCount {
    // 序列佇列同步執行
    dispatch_sync(self.synchronizationQueue, ^{
        // 減少當前正在活動的請求數量
        if (self.activeRequestCount > 0) {
            self.activeRequestCount -= 1;
        }
    });
}

/**
 如果必要的話安全的開啟下一個任務
 */
- (void)safelyStartNextTaskIfNecessary {
    // 序列佇列同步執行
    dispatch_sync(self.synchronizationQueue, ^{
        // 如果當前活動的請求數量沒有到達上限
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            // 如果當前排隊中有合併任務
            while (self.queuedMergedTasks.count > 0) {
                // 獲取到排隊中的第一個合併任務物件
                AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
                // 如果合併任務物件的任務的狀態是暫停,就啟動這個任務
                if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
                    [self startMergedTask:mergedTask];
                    break;
                }
            }
        }
    });
}

/**
 啟動合併任務物件
 */
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    // 啟動合併任務物件中的任務
    [mergedTask.task resume];
    // 增加當前活動請求數量
    ++self.activeRequestCount;
}

/**
 將合併任務物件新增到排隊中
 */
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    switch (self.downloadPrioritizaton) {
        case AFImageDownloadPrioritizationFIFO:
            // 如果優先順序是先進先出,就新增到最後一個
            [self.queuedMergedTasks addObject:mergedTask];
            break;
        case AFImageDownloadPrioritizationLIFO:
            // 如果優先順序是後進先出,就插入到第一個
            [self.queuedMergedTasks insertObject:mergedTask atIndex:0];
            break;
    }
}

/**
 獲取到排隊中的第一個合併任務物件
 */
- (AFImageDownloaderMergedTask *)dequeueMergedTask {
    AFImageDownloaderMergedTask *mergedTask = nil;
    mergedTask = [self.queuedMergedTasks firstObject];
    [self.queuedMergedTasks removeObject:mergedTask];
    return mergedTask;
}

/**
 判斷當前活動請求數量是否達到限制數量
 */
- (BOOL)isActiveRequestCountBelowMaximumLimit {
    return self.activeRequestCount < self.maximumActiveDownloads;
}
複製程式碼

原始碼閱讀系列:AFNetworking

原始碼閱讀:AFNetworking(一)——從使用入手

原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization

原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization

原始碼閱讀:AFNetworking(四)——AFSecurityPolicy

原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

原始碼閱讀:AFNetworking(六)——AFURLSessionManager

原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager

原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

原始碼閱讀:AFNetworking(九)——AFImageDownloader

原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

原始碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章