AFNetworking-数据流程

AFNetworking


AFNetWorking基本上是所有iOS项目的标配。现在升级带最新版的3.X了。得益于苹果从NSURLConnection升级到NSURLSession,AFN也实现了api的简化,同时功能却一点没少。我们来看一下AFN3.X的目录结构:

  • AFNetWorking 这个文件是一个头文件。啥也没做,就是引入了其他文件方便使用。
  • AFURLSessionManager 这个文件是核心类,基本上通过它来实现了大部分核心功能。负责请求的建立、管理、销毁、安全、请求重定向、请求重启等各种功能。他主要实现了NSURLSession和NSRULSessionTask的封装。
  • AFHTTPSessionManager 这个文件是AFURLSessionManager的子类。主要实现了对HTTP请求的优化。
  • AFURLRequestSerialization 这个主要用于请求头的编码解码、序列化、优化处理、简化请* 求拼接过程等。
  • AFURLResponseSerialization 这个主要用于网络返回数据的序列化、编码解码、序列化、数据处理等。
  • AFSecurityPolicy 这个主要用于请求的认证功能。比如https的认证模式等。
  • AFNetworkReachabilityManager 这个主要用于监听网络请求状态变化功能。

我们在看AFNetworking内部时候,首先我们先搞清什么是NSURLSession,苹果原生的请求是如何运行的。

NSURLSession


Session.png

从图中我们可以看出在NSURLSession中比较重要的几个对象,一个是NSURLSessionTaskNSURLSessionConfiguration,还有的为它请求时执行的代理方法。

NSURLSessionTask.png

从这几个子类的名字就可以大概猜出他们的作用了.接下来我们就从不同类型的任务出发,来使用session.
使用NSURLSession,拢共分两步:

  1. 通过NSURLSession的实例创建task
  2. 通过task选择自己所需要的方法,有Delegate方法Block方法
  3. 执行task

task一共有4个delegate,只要设置了一个,就代表四个全部设置,有时候一些delegate不会被触发的原因在于这四种delegate是针对不同的URLSession类型和URLSessionTask类型来进行响应的,也就是说不同的类型只会触发这些delegate中的一部分,而不是触发所有的delegate。

通过一个例子来查看下请求一个数据时,数据如何发送和接收到数据。

Block方式接收数据

- (IBAction)orginalDownLoadAction:(UIButton *)sender {
    __weak typeof(self) weakself = self;
    NSURLSession *session = [self createASession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[self creatRequest:downURL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            weakself.showImageView.image = [UIImage imageWithData:data];
        });
    }];
    [dataTask resume];
}

//  创建Session对象
- (NSURLSession *)createASession {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    operationQueue.maxConcurrentOperationCount = 1;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:operationQueue];
    return session;
}

- (NSURLRequest *)creatRequest:(NSString *)url {
    NSURLRequest *requset = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    return requset;
}

使用Block很简单,只需要传入请求的Request就可以直接获取到数据。
使用Delegate方式,才可以真正的观测到数据的获取和请求的发送

- (IBAction)orginalDownLoadAction:(UIButton *)sender {
    NSURLSession *session = [self createASession];
    
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:downURL]];
    
    [dataTask resume];
}
/**
 接收到服务器的响应
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{ 
 
    _mutableData = [[NSMutableData alloc] init];
     // 允许处理服务器的响应,才会继续接收服务器返回的数据
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

/**
 接收到服务器的数据(可能调用多次)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    NSLog(@"接收数据返回");
    [_mutableData appendData:data];
}

/**
 请求成功或者失败(如果失败,error有值)
 所有的代理都会执行此代理方法
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error) {
        NSLog(@"下载有误 %@",[error localizedDescription]);
    }
    else {
        NSLog(@"完成下载");
        __weak typeof(self) weakself = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            weakself.showImageView.image = [UIImage imageWithData:_mutableData];
        });
    }
}

如果使用下载downloadTaskWithRequest:方法会调用别的代理方法

/**
 *  写数据
 *
 *  @param session                 会话对象
 *  @param downloadTask            下载任务
 *  @param bytesWritten            本次写入的数据大小
 *  @param totalBytesWritten       下载的数据总大小
 *  @param totalBytesExpectedToWrite 文件的总大小
 */
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    __weak typeof(self) weakself = self;
    CGFloat percent = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    NSLog(@"执行下载中 %.1lf%%",percent * 100);
    dispatch_async(dispatch_get_main_queue(), ^{
        [weakself.progressSlider setValue:percent animated:YES];
    });
}

/**
 下载完成的事件  
 @param location 数据存放位置,一定要在这个函数返回之前,对数据进行使用,或者保存
 */
- (void)URLSession:(NSURLSession *)session  downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"完成下载");
}

看完了对于苹果原生的下载流程,接下来我们来看看AFNetworking对于这套流程是做了怎样的处理,如何方便我们使用这套流程。

AFNetworking源码分析


AFNetworking.png

AFNetworking主要模块有个模块:

  • AFURLSessionManager
  • AFHTTPSessionManager
  • AFURLRequestSerialtion
  • AFNetworkReachabilityManager
  • AFSecurityPolicy

五个模块,其中AFURLSessionManager是核心类,也是AFHTTPSessionManager的父类。这类中控制所有NSURLSession的代理数据回调AFHTTPSessionManager是对外面做一个接口的显示,对于外部方便调用和请求的数据的处理,还有就是让AFURLSessionManager专门只需要负责数据的回调和处理工作。
首先我们从这个AFURLSessionManager核心类说起

上图为AFURLSessionManager中主要的类方法。我们先看这个类的头文件都有什么

AFURLSessionManager头文件属性:

interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
//指定的初始化方法、通过他来初始化一个Manager对象。
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration 
//AFURLSessionManager通过session来管理和创建网络请求。一个manager就实现了对这个session的管理,他们是一一对应的关系。
@property (readonly, nonatomic, strong) NSURLSession *session;
//处理网络请求回调的操作队列,就是我们初始化session的时候传入的那个OperationQueue参数。如果不传入,默认是MainOperationQueue。
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
//对返回数据的处理都通过这个属性来处理,比如数据的提取、转换等。默认是一个`AFJSONResponseSerializer`对象用JSON的方式解析。
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
//用于指定session的安全策略。用于处理信任主机和证书认证等。默认是`defaultPolicy`。
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
//观测网络状态的变化,具体可以看我的Demo用法。
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
@end

在这里在罗列这个类中使用的通知

  • AFNetworkingTaskDidResumeNotification
  • AFNetworkingTaskDidCompleteNotification
  • AFNetworkingTaskDidSuspendNotification
  • AFURLSessionDidInvalidateNotification
  • AFURLSessionDownloadTaskDidFailToMoveFileNotification
  • AFNetworkingTaskDidCompleteResponseDataKey
  • AFNetworkingTaskDidCompleteSerializedResponseKey
  • AFNetworkingTaskDidCompleteResponseSerializerKey
  • AFNetworkingTaskDidCompleteAssetPathKey
  • AFNetworkingTaskDidCompleteErrorKey

在外面通过监听这些通知,可以获取到这些通知的信息。

之后我们再来看看.m中具体如何进行数据的回调和处理

最开头有个宏定义的目的是通过NSFoundation的版本来判断当前ios版本

#ifndef NSFoundationVersionNumber_iOS_8_0
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11
#else
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
#endif

之后在文件的头部看到多个单利对象。
在AFNetworking中所有的和创建任务相关的事件都放到了一个单例的队列中,

static dispatch_queue_t url_session_manager_creation_queue() {
    static dispatch_queue_t af_url_session_manager_creation_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
    });

    return af_url_session_manager_creation_queue;
}

static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        // Fix of bug
        // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
        // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}

之后的任务创建安全的单利,主要是为了解决当task1执行后,数据还没返回,task2就创建,这时task1的数据返回了就会调用task2的数据返回方法。这样就会造成一个bug,造成数据创建的不安全。
接下来还有一个url_session_manager_processing_queue单利对象,这个方法是创建一个队列用来管理数据的处理。和上边的创建的方法对比,这个方法创建的队列是一个并行的队列,这就加快了数据的处理速度。

使用一个简单AFNetworking请求

/** AFNetworking数据请求 */
    self.manager = [AFHTTPSessionManager manager];
    self.manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    _task = [self.manager dataTaskWithRequest:[self creatRequest:downURL] completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                weakself.showImageView.image = [UIImage imageWithData:responseObject];
            });
        });
    }];

1、内部实现的话,就是

@param configuration NSURLSession的配置
 @return 返回一个manager对象
 */
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    //如果用户没有手动指定,则使用默认的configuration来初始化
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    //赋值给属性
    self.sessionConfiguration = configuration;
    //初始化NSURLSession的task代理方法执行的队列。
    //这里有一个很关键的点是task的代理执行的queque一次性只能执行一个task。这样就避免了task的代理方法执行的混乱。
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;
    //出丝滑NSURLSession对象,最核心的对象。
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    //如果用户没有手动指定,则返回的数据是JSON格式序列化。
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    //指定https处理的安全策略。
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
    //初始化网络状态监听属性
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
    //用于记录Task与他的`AFURLSessionManagerTaskDelegate`代理对象的一一对应关系。通过这个
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
    //初始化一个锁对象,关键操作加锁。
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;
    /**
     获取当前session正在执行的所有Task。同时为每一个Task添加`AFURLSessionManagerTaskDelegate`代理对象,这个代理对象主要用于管理uplaodTak和downloadTask的进度管理。并且在Task执行完毕以后调用相应的Block。同时发送相应的notification对象,实现对task数据或者状态改变的检测。
     @param dataTasks dataTask列表
     @param uploadTasks uplaodTask列表
     @param downloadTasks downloadTask列表
     @return
     */
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        for (NSURLSessionDataTask *task in dataTasks) {
            [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }
        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }
        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];
    return self;
}

这段代码主要核心是self.session的Block获取当前执行的task为每个task添加一个监听对象Delegate和相应的task数据变化监听。
2、[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; 这句话来为Task设置一个AFURLSessionManagerTaskDelegate代理对象。从而可以实现对进度处理、Block调用、Task完成返回数据的拼装的功能。

//根据指定的Task,初始化一个AFURLSessionManagerTaskDelegate
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
    delegate.manager = self;
    //设置Task完成的回调Block
    delegate.completionHandler = completionHandler;
    if (destination) {
        //任务完成以后,调用destination这个Block
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            return destination(location, task.response);
        };
    }
    //指定Task与taskDescriptionForSessionTasks的关联关系,方便后面的通知中做对应的处理。
    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
    //添加通知
    [self setDelegate:delegate forTask:downloadTask];
    //设置一个下载进度的Block,以便在后面代理方法中调用。
    delegate.downloadProgressBlock = downloadProgressBlock;

3、在初始化AFURLSessionManagerTaskDelegate时候,在这个对象定义了请求的过程对象和控制
这个代理主要目的是:

  • 处理上传或者下载的进度
  • 处理获取完数据后的操作行为
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;//接收数据
@property (nonatomic, strong) NSProgress *uploadProgress;//上传进度
@property (nonatomic, strong) NSProgress *downloadProgress;//下载进度
@property (nonatomic, copy) NSURL *downloadFileURL;//下载路径
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;//返回下载后完成的文件路径
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;//上传时候调用
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;//下载时候调用
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;//完成调用
@end

从这些文件的属性,我们需要注重了解一个对象类NSProgress,这个是Apple为了管理进度在iOS7时候增加的。如果我们在开发时候遇到有关于进度的事情,应该尽量考虑。它内部使用的是KVO机制来监听进度值的变化。

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //这个属性用于存储Task下载过程中的数据
    self.mutableData = [NSMutableData data];
    //存储Task上传和下载的进度
    self.uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    self.uploadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;

    self.downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    self.downloadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown;
    return self;
}

#pragma mark - NSProgress Tracking

- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [self.uploadProgress setCancellable:YES];
    //当progress对象取消的时候,取消Task
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];
    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];
              
//更新progress的进度来获取Task的进度。fractionCompleted方法在请求过程中多次执行。
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}

- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))];
    [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
    [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}
//上面通过对fractionCompleted方法KVO。则会调用下面的方法,从而执行manager的
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

4、在AFURLSessionManagerTaskDelegate设置Task状态改变的监听。

 /**
 设置指定task的`AFURLSessionManagerTaskDelegate`对象。并且添加task挂起或者重启的监听。
 @param delegate 代理对象
 @param task task
 */
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);
    //加锁操作
    [self.lock lock];
    //为Task设置与之代理方法关联关系。通过一个字典
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    //添加对Task开始、重启、挂起状态的通知的接收。
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

5、从下面开始,任务就正式开始执行。其实就是[downloadTask resume];执行以后开始。

/**
 在网络请求正式开始以后,这个方法会在数据接收的过程中多次调用。我们可以通过这个方法获取数据下载的大小、总得大小、还有多少么有下载
 @param session session
 @param downloadTask 对应的Task
 @param bytesWritten 已经下载的字节
 @param totalBytesWritten 总的字节大小
 @param totalBytesExpectedToWrite nil
 */
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //获取Task对应的`AFURLSessionManagerTaskDelegate`对象。从而可以调用对应的代理方法
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (delegate) {
        //调用`AFURLSessionManagerTaskDelegate`类中的代理方法。从而实现对于进度更新等功能。
        //会调用下面的那个方法
        [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
    }
    if (self.downloadTaskDidWriteData) {
        //如果有`downloadTaskDidWriteData`Block的实现,则在这个调用Block从而实现对下载进度过程的控制。
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}
//AFURLSessionManagerTaskDelegate里面的这个代理方法实现对进度的更新。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    //AFURLSessionManagerTaskDelegate代理方法实现对下载进度的记录
    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

6、Task完成以后,会调用AFURLSessionManagerTaskDelegate对象的方法对返回的数据封装。

#pragma mark - NSURLSessionTaskDelegate
//AFURLSessionManagerTaskDelegate里面的这个代理方法实现对数据的具体处理。
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//获取Task对应的manager对象
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;
//要封装的responseObject对象
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
    //返回的数据。
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }
//如果是downloadTask,则封装downloadFileURL
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {//如果是其他Task,则封装返回的data。
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }
 //发送一个指定Task结束的通知
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
    //在一个并行的dispat_queuq_t对象里面异步处理。
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            //封装responseBojct,对数据做序列化处理
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
#pragma clang diagnostic pop
}

7、 移除Task对应的通知和对应的AFURLSessionManagerTaskDelegate代理对象。

- (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    [self.lock lock];
    //移除Task对应的通知
    [self removeNotificationObserverForTask:task];
    //移除Task对应的`AFURLSessionManagerTaskDelegate`代理对象。
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}
//移除通知监听
- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task];
}
//`AFURLSessionManagerTaskDelegate`对象回收。
- (void)dealloc {
    [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
    [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}

通过上面的过程,我们发现核心流程都是围绕了NSRULSessionTask对象以及与之绑定的AFURLSessionManagerTaskDelegate对象执行的。我们通过在NSRULSessionTask对象的代理方法里面手动调用AFURLSessionManagerTaskDelegate对应的代理方法来实现对数据的处理和简化代码的作用。还有一些方法没有涉及到,不过大同小异

AFURLSessionManager一些特殊模块

有的时候,我们的请求会返回302这个状态码,这个表示需要请求重定向到另一个url,我们可以下面这个代理方法里面决定对于重定向的处理,如果对completionHandler传入nil,则会把response传入重定向请求。另外,backgroundSession的Task不会调用下面这个代理方法,而是直接调用。

/**
 有的时候,我们的请求会返回302这个状态码,这个表示需要请求重定向到另一个url,我们可以在这个代理方法里面绝定对于重定向的处理。
 @param session session
 @param task task
 @param response response
 @param request 重定向的request。
 @param completionHandler 请求完成
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    //重定向的request对象
    NSURLRequest *redirectRequest = request;
    //如果用户指定了taskWillPerformHTTPRedirection这个Block,我们就通过这个Block的调用返回处理完成的request对象。
    if (self.taskWillPerformHTTPRedirection) {
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }
    //这个调用是必须的,执行重定向操作。
    if (completionHandler) {
        completionHandler(redirectRequest);
    }
}

创建NSRULSessionUplaodTask的时候,在某些系统上会出现bug。AFN已经帮我们处理好:

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    __block NSURLSessionUploadTask *uploadTask = nil;
    //用线程安全的方式创建一个dataTask。修复iOS8下面的bug。
    url_session_manager_create_task_safely(^{
        uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
    });
    //用于处理uploadTask在iOS7环境下面有可能创建失败的情况。如果attemptsToRecreateUploadTasksForBackgroundSessions为true。则尝试重新创建Task。如果三次都没有成功,则放弃。
    if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) {
        for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {
            uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
        }
    }
    //为Task添加`AFURLSessionManagerTaskDelegate`代理方法
    [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    return uploadTask;
}

通过使用dispatch_semaphore_t来控制对异步处理返回结果的控制。非常有借鉴意义。

#pragma mark -  获取当前session对应的task列表。通过dispatch_semaphore_t来控制访问过程。
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }
        //这里发送一个信号量,让semaphore变为1。此时表示tasks已经成功获取。
        dispatch_semaphore_signal(semaphore);
    }];
    //这里会一直等待信号量变为1。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //返回Task。通过信号量控制,避免了方法结束的时候,tasks还没有正常获取的情况。
    return tasks;
}

总结

AFURLSessionManager通过对task设置一个AFURLSessionManagerTaskDelegate代理来处理繁杂的请求进度管理。从而降低了代码的负责度。是代理模式的一个很好的实践。

AFURLSessionManager通过私有类_AFURLSessionTaskSwizzling来修改iOS7和iOS8系统上面不同。是对于方法swizzle的一个成功和完整的实践。

AFURLSessionManager通过添加各种Block,让我们对请求过程有全方位的控制和处理。同时提供简洁的api,把负责的处理全部封装好。


D0832F85-DF51-47BD-999E-764B13E56E39.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容