AFNetworking 3.0 源码解析之UIKit

作者: SemyonXu | 来源:发表于2016-12-06 18:23 被阅读347次

    本部分主要的作用:对UI控件进行网络请求的支持。

    UIKit

    功能:本部分主要方便UI使用网络功能,为UIImageView,UIButton等设置网络下载图片。功能类似于SDWebImage库。以及UIProgressView,UIWebView,UIRefreshControl,UIActivityIndicatorView部分UI的网络使用封装。

    下面分别解析一下各个控件的设计思路

    UIImageView+AFNetworking

    为UIImageView加载网络图片提供便利的接口。

    使用方法

    - (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)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
    {
    
        if ([urlRequest URL] == nil) {
            [self cancelImageDownloadTask];
            self.image = placeholderImage;
            return;
        }
    
        if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){ // - 检测是当前任务是否有这个urlRequest
            return;
        }
    
        [self cancelImageDownloadTask];
    
        AFImageDownloader *downloader = [[self class] sharedImageDownloader];
        id <AFImageRequestCache> imageCache = downloader.imageCache;
    
        //Use the image from the image cache if it exists
        UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
        if (cachedImage) {
            if (success) {
                success(urlRequest, nil, cachedImage);
            } else {
                self.image = cachedImage;
            }
            [self clearActiveDownloadInformation];
        } else {
            if (placeholderImage) {
                self.image = placeholderImage;
            }
    
            __weak __typeof(self)weakSelf = self;
            NSUUID *downloadID = [NSUUID UUID]; // 下载标识符
            AFImageDownloadReceipt *receipt; // 完成标识符,下载跟完成是一样的
            receipt = [downloader
                       downloadImageForURLRequest:urlRequest
                       withReceiptID:downloadID
                       success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
                           __strong __typeof(weakSelf)strongSelf = weakSelf;
                           if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                               if (success) { // 成功返回
                                   success(request, response, responseObject);
                               } else if(responseObject) { // 成功但是不是请求成功,用缓存图片,直接给imageview赋值
                                   strongSelf.image = responseObject;
                               }
                               [strongSelf clearActiveDownloadInformation];
                           }
    
                       }
                       failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                           __strong __typeof(weakSelf)strongSelf = weakSelf;
                            if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                                if (failure) {
                                    failure(request, response, error);
                                }
                                [strongSelf clearActiveDownloadInformation];
                            }
                       }]; // 注意,这个是同步的,所以可以获得receipt
    
            self.af_activeImageDownloadReceipt = receipt;
        }
    }
    

    首先会对上次的任务进行检测,如果有的话,取消掉。

        if (self.af_activeImageDownloadReceipt != nil) {
            [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt]; // runtime 管理对象获取downloader单利,取消当前这个receipt下的任务
            [self clearActiveDownloadInformation]; // 清空 af_activeImageDownloadReceipt
         }
    }
    

    这里检测的是self.af_activeImageDownloadReceipt,注意这是分类中添加的属性,下载的唯一标识符。也就是说如果这个任务已经下载过的话,下次重新下载会取消上次的下载任务。

    类别添加属性的方法是用的runtime:

    - (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
        return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
    }
    
    - (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
        objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    

    检测如果本地有缓存,直接获取缓存的图片,返回

    缓存类是:AFAutoPurgingImageCache。
    缓存类的封装思路:

    AFAutoPurgingImageCache

    简单的图片缓存,比起SDWebImage来说比较简单,没有分别存内存和硬盘。

    @property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
    @property (nonatomic, assign) UInt64 currentMemoryUsage;
    @property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
    

    cachedImages:缓存字典
    currentMemoryUsage:当前使用的内存字节大小
    synchronizationQueue:同步队列,主要用来做同步操作

    初始化方法:
    - (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
        if (self = [super init]) {
            self.memoryCapacity = memoryCapacity;
            self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
            self.cachedImages = [[NSMutableDictionary alloc] init];
    
            NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
            self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    
            [[NSNotificationCenter defaultCenter]
             addObserver:self
             selector:@selector(removeAllImages)
             name:UIApplicationDidReceiveMemoryWarningNotification
             object:nil];
    
        }
        return self;
    }
    

    同步队列是用的自定义并行队列。这里有对内存警告的处理。收到内存警告的通知:UIApplicationDidReceiveMemoryWarningNotification,执行清空所有缓存的图片removeAllImages方法。

    添加图片:
    - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
        dispatch_barrier_async(self.synchronizationQueue, ^{
            AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
    
            AFCachedImage *previousCachedImage = self.cachedImages[identifier];
            if (previousCachedImage != nil) {
                self.currentMemoryUsage -= previousCachedImage.totalBytes;
            }
    
            self.cachedImages[identifier] = cacheImage;
            self.currentMemoryUsage += cacheImage.totalBytes;
        });
    
        dispatch_barrier_async(self.synchronizationQueue, ^{
            if (self.currentMemoryUsage > self.memoryCapacity) {
                UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
                NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
                NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
                                                                               ascending:YES];
                [sortedImages sortUsingDescriptors:@[sortDescriptor]];
    
                UInt64 bytesPurged = 0;
    
                for (AFCachedImage *cachedImage in sortedImages) {
                    [self.cachedImages removeObjectForKey:cachedImage.identifier];
                    bytesPurged += cachedImage.totalBytes;
                    if (bytesPurged >= bytesToPurge) {
                        break ;
                    }
                }
                self.currentMemoryUsage -= bytesPurged;
            }
        });
    }
    

    使用的是GCD的barrier异步方法,在保证队列中前面的任务全部完成的情况下,执行添加方法,并不阻塞之后的任务执行。

    1. 首先初始化一个AFCachedImage对象,用这个对象存图片,以及一些图片相关的信息。缓存字节数响应的增加调整。如果已经存在的图片进行处理,那么先减去缓存字节数,最后替换图片。
    2. 对缓存内容大小进行判断,使用barrier可以保证在上个添加任务完成后再执行下个任务。如果缓存大小超过总的缓存容量,那么首先执行排序,然后顺序移除,直到不再超出缓存容量。
    移除图片

    移除图片类似使用的GCD的barrier同步方法。这里会阻塞,等待移除任务做完之后,进行以后的任务。跟添加有区别,这样可以保证线程安全。
    上代码:

    - (BOOL)removeImageWithIdentifier:(NSString *)identifier {
        __block BOOL removed = NO;
        dispatch_barrier_sync(self.synchronizationQueue, ^{
            AFCachedImage *cachedImage = self.cachedImages[identifier];
            if (cachedImage != nil) {
                [self.cachedImages removeObjectForKey:identifier];
                self.currentMemoryUsage -= cachedImage.totalBytes;
                removed = YES;
            }
        });
        return removed;
    }
    
    - (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;
    }
    

    这里支持单个删除,全部删除。操作的还是当前这个cachedImages字典。

    网络下载,并把获取到的图片赋值给当前的ImageView

    首先,会先设置一个默认的placeholderImage占位图,如果你设置过的话。
    然后,这里使用的是[NSUUID UUID]来做下载的唯一标识符,注意downloadID如receipt是一样的。这个是用来区别是否是当前任务。
    下载任务是用的:AFImageDownloader。这是UI下载里面的核心部分。
    通过调用下载方法,获取到下载图片结果。
    如果成功,并且receiptID匹配当前的下载downloadID,有成功回调的话,返回去成功的参数已经图片。如果没有成功回调的话,直接给uiimageView赋值(这个地方跟SDWebImage还是有区别的,SDWebImage直接赋值,就算有成功回调也会赋值)。然后清空当前的下载信息,其实就是清空这个标示id。
    如果失败,如果有失败block的情况,回调回去,没有不做任何处理,相同的清空下载信息。
    最后有个赋值:
    self.af_activeImageDownloadReceipt = receipt;
    这个地方之所以可以这么赋值,是因为下载方法是同步的。注意的是这个任务是同步的,但是发起的网络请求是异步的。

    下面解析一下AFImageDownloader.

    AFImageDownloader

    核心下载类,下载任务,取消任务。

    开始下载任务
    - (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) Append the success and failure blocks to a pre-existing request if it already exists
            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) Attempt to load the image from the image cache if the cache policy allows it
            switch (request.cachePolicy) {
                case NSURLRequestUseProtocolCachePolicy:
                case NSURLRequestReturnCacheDataElseLoad: // 只有在cache中不存在data时才从原始地址下载。
                case NSURLRequestReturnCacheDataDontLoad: { // 只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式
                    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) Create the request and set up authentication, validation and response serialization
            NSUUID *mergedTaskIdentifier = [NSUUID UUID];
            NSURLSessionDataTask *createdTask;
            __weak __typeof__(self) weakSelf = self;
    
            createdTask = [self.sessionManager
                           dataTaskWithRequest:request
                           completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                               dispatch_async(self.responseQueue, ^{
                                   __strong __typeof__(weakSelf) strongSelf = weakSelf;
                                   AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
                                   if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                       mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                       if (error) { // 失败返回回调
                                           for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                               if (handler.failureBlock) {
                                                   dispatch_async(dispatch_get_main_queue(), ^{
                                                       handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                                   });
                                               }
                                           }
                                       } else { // 成功返回回调,添加图片到缓存
                                           [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
    
                                           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) Store the response handler for use when the request completes
            AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                       success:success
                                                                                                       failure:failure];
            AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                       initWithURLIdentifier:URLIdentifier
                                                       identifier:mergedTaskIdentifier
                                                       task:createdTask];
            [mergedTask addResponseHandler:handler]; // 一个mergedTask添加一个handler,为什么要把handler设计成一个数组呢?
            self.mergedTasks[URLIdentifier] = mergedTask;
    
            // 5) Either start the request or enqueue it depending on the current active request count
            if ([self isActiveRequestCountBelowMaximumLimit]) { // 不到最大并发数,开启新任务
                [self startMergedTask:mergedTask];
            } else { // 超出最大并发数,加入缓存队列
                [self enqueueMergedTask:mergedTask];
            }
    
            task = mergedTask.task; // createdTask
        if (task) {
            return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task]; // 为task打标签
        } else {
            return nil;
        }
    }
    

    首先处理失败的回调,如果失败,抛出失败的request,error,直接返回。
    成功的话,分三部分处理

    1. 判断当前task是否已经存在,如果存在,直接获取这个task,并返回。
    2. 从缓存里面取,如果已经缓存过,调用成功回调,把缓存的图片返回回去。
    3. 如果以上两点都没有的,进行网络请求。这里使用的是AFURLSessionManager里面的
    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    

    方法。这个方法的解析参考NSURLSession部分的解析。
    回调中,这里会判断是否是当前任务URLIdentifier,如果是,继续处理。并分别返回成功失败的回调。
    处理完之后,这里会将队列任务数量-1,然后开启下一个缓存的任务。

    [strongSelf safelyDecrementActiveTaskCount];
    [strongSelf safelyStartNextTaskIfNecessary];
    

    由于是同步的,所以我们可以获取这个createdTask任务。
    初始化一个AFImageDownloaderResponseHandler对象,这个对象主要负责成功失败的回调,以及唯一标示每个任务的id。然后添加到AFImageDownloaderMergedTask的对象中,AFImageDownloaderMergedTask类主要作用:统一管理task的标识符,回调对象。主要这里有两个标识符:

    • URLIdentifier:用于标示下载任务唯一。内部使用AFImageDownload使用。
    • identifier:用于匹配下载任务与完成的处理唯一。外部类使用。

    然后处理缓存队列,如果没有到最大并发数,开启新任务,如果超出最大并发数,加入缓存队列。

    看一下入队,出队的代码:

    - (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; // 返回最先进入队列的任务,先进先出
    }
    

    入队的设计还是使用的数组,支持先进先出,后进先出两种入队模式。

    取消下载任务
    - (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];
                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];
            }
        });
    }
    
    1. 通过URLIdentifier获取当前mergedTask任务,通过receiptID遍历出当前的这个handler。
    2. 将handler从mergedTask中移除,抛出失败的回调。
    3. 最后移除当前的这个mergedTask

    UIButton+AFNetworking

    封装思路跟UIImageView+AFNetworking基本相同,区别在于图片的处理,UIButton分别处理了图片:
    [self setImage:cachedImage forState:state];
    背景图片:
    [self setBackgroundImage:placeholderImage forState:state];
    已经对不同状态的处理。

    UIProgressView+AFNetworking

    支持下载、上传的进度条,主要使用的技术点是KVO。

    设置调用方法:

    - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
                                       animated:(BOOL)animated
    {
        [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
        [task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
    
        [self af_setUploadProgressAnimated:animated];
    }
    
    - (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
                                         animated:(BOOL)animated
    {
        [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
        [task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
    
        [self af_setDownloadProgressAnimated:animated];
    }
    

    这里对上传任务的key:state,countOfBytesSent进行了监听,
    对下载任务key:state,countOfBytesReceived进行了监听。

    支持动画设置:

    - (BOOL)af_uploadProgressAnimated {
        return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
    }
    
    - (void)af_setUploadProgressAnimated:(BOOL)animated {
        objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (BOOL)af_downloadProgressAnimated {
        return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
    }
    
    - (void)af_setDownloadProgressAnimated:(BOOL)animated {
        objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    使用runtime的关联对象功能,也是蛮拼的,这么一个小的属性的支持。

    核心:kvo的监听回调中

    #pragma mark - NSKeyValueObserving
    
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(__unused NSDictionary *)change
                           context:(void *)context
    {
        if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
            if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
                if ([object countOfBytesExpectedToSend] > 0) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
                    });
                }
            }
    
            if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
                if ([object countOfBytesExpectedToReceive] > 0) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
                    });
                }
            }
    
            if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
                if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
                    @try {
                        [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
    
                        if (context == AFTaskCountOfBytesSentContext) {
                            [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
                        }
    
                        if (context == AFTaskCountOfBytesReceivedContext) {
                            [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
                        }
                    }
                    @catch (NSException * __unused exception) {}
                }
            }
        }
    }
    
    1. 首先对countOfBytesExpectedToSend和countOfBytesExpectedToReceive分别处理:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f,[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f)。两个百分比的计算方式不同。
    2. 再处理state,如果当前状态是完成:NSURLSessionTaskStateCompleted,移除当前的kvo对象。使用kvo的时候注意移除,否则会导致内存泄露。

    UIRefreshControl+AFNetworking

    系统的刷新类的支持,使用的是从AFURLSessionManager里抛出的当前task状态通知:
    AFNetworkingTaskDidResumeNotification,
    AFNetworkingTaskDidSuspendNotification,
    AFNetworkingTaskDidCompleteNotification。

     - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task {
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    
        [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
        [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
        [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
    
        if (task) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wreceiver-is-weak"
    #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
            if (task.state == NSURLSessionTaskStateRunning) {
                [self.refreshControl beginRefreshing];
    
                [notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingTaskDidResumeNotification object:task];
                [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidCompleteNotification object:task];
                [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidSuspendNotification object:task];
            } else {
                [self.refreshControl endRefreshing];
            }
    #pragma clang diagnostic pop
        }
    }
    

    主要在resume状态的时候进行刷新动画,在complete、suspend状态下停止刷新动画。

    UIActivityIndicatorView+AFNetworking

    跟UIRefreshControl封装思路一样。

    UIWebView+AFNetworking

    支持UIWebView的下载,网络部分使用的是NSURLSession部分的AFHTTPSessionManager的get方法。

    - (void)loadRequest:(NSURLRequest *)request
               MIMEType:(NSString *)MIMEType
       textEncodingName:(NSString *)textEncodingName
               progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
                success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
                failure:(void (^)(NSError *error))failure
    {
        NSParameterAssert(request);
    
        if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
            [self.af_URLSessionTask cancel];
        }
        self.af_URLSessionTask = nil;
    
        __weak __typeof(self)weakSelf = self;
        NSURLSessionDataTask *dataTask;
        dataTask = [self.sessionManager
                GET:request.URL.absoluteString
                parameters:nil
                progress:nil
                success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                    __strong __typeof(weakSelf) strongSelf = weakSelf;
                    if (success) {
                        success((NSHTTPURLResponse *)task.response, responseObject);
                    }
                    [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]];
    
                    if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
                        [strongSelf.delegate webViewDidFinishLoad:strongSelf];
                    }
                }
                failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
                    if (failure) {
                        failure(error);
                    }
                }];
        self.af_URLSessionTask = dataTask;
        if (progress != nil) {
            *progress = [self.sessionManager downloadProgressForTask:dataTask];
        }
        [self.af_URLSessionTask resume];
    
        if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
            [self.delegate webViewDidStartLoad:self];
        }
    }
    

    请求完成后, 如果成功会:
    1)回调成功的数据
    2)当前webview加载成功的数据
    3)使用代理触发webViewDidStartLoad回调。

    失败直接回调回去失败的error。

    请求开始后,立刻触发webViewDidStartLoad的回调。对WebView的回调支持比较好。

    如果文中有什么错误,欢迎大家指正。

    更多问题讨论欢迎加QQ群:200792066

    转载请注明出处:http://semyonxu.com

    相关文章

      网友评论

        本文标题:AFNetworking 3.0 源码解析之UIKit

        本文链接:https://www.haomeiwen.com/subject/ekvrmttx.html