源码阅读:AFNetworking(九)——AFImageDownloader

该文章阅读的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值