源碼閱讀:AFNetworking(九)——AFImageDownloader

該文章閱讀的AFNetworking的版本爲3.2.0。數組

該類用於圖片的下載緩存

1.枚舉

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

該枚舉定義了圖片下載的優先級:安全

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

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

2.公共私有類

2.1.AFImageDownloadReceipt類

這個類是一個公共類,它是對一個task的封裝,並用UUID做爲標識符。session

2.1.1.接口

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

/**
 標識符
 */
@property (nonatomic, strong) NSUUID *receiptID;
複製代碼

2.1.2.實現

實現很是簡單,只有一個自定義的初始化方法,將task和標識符進行保存。async

- (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做爲標識符ide

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.實現

它的實現一樣也是很簡單,一個自定義初始化方法,一個打印數據方法post

- (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做爲標識符ui

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

相關文章
相關標籤/搜索