iOS开发那点儿事AFNetworkingiOS 修炼之路

AFNetworking 3.0 源码解析之UIKit

2016-12-06  本文已影响347人  SemyonXu

本部分主要的作用:对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的标识符,回调对象。主要这里有两个标识符:

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

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

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

上一篇下一篇

猜你喜欢

热点阅读