iOS源码补完计划--AFNetworking(一)

目录

  • 前言
  • AFURLSessionManager
    • 业务流程
    • NSURLSession的代理方法
    • AFURLSessionManagerTaskDelegate
      • 任务进度
      • 承接方法
  • AFHTTPSessionManager
    • 业务流程
    • 分片上传
  • 一些比较有意思的东西
    • 在监听属性的时候、可以用NSStringFromSelector(@selector(xxx))这种方式来自动提示。
    • 功能AIP分层
    • 如何防止block循环引用
    • 把NSURLSession众多代理转化成了block
    • 消除编译器clang警告
    • 正则的简便写法
    • 如何做到对外只读、对内读写
  • API注释Demo
  • 参考

前言

AFNetworking源码第一篇
主要看了看AFURLSessionManager以及AFHTTPSessionManager相关的API

AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》

AFURLSessionManager

业务流程

先写一个最简单的网络请求

NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager * manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
    
//如果不添加反序列化、有可能会报错说传回来的res是text/html。
manager.responseSerializer = [AFHTTPResponseSerializer serializer];

NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
NSURLSessionDataTask * task = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        
}];
    
[task resume];
进入AFURLSessionManager内部:

初始化一个新的AFURLSessionManager对象

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }

    //容错
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    self.operationQueue = [[NSOperationQueue alloc] init];
    //最大并发为1
    self.operationQueue.maxConcurrentOperationCount = 1;
    
    //初始化自己持有的NSURLSession对象
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

    //设置数据序列化类型
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    
    //设置安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    
    //设置网络监控
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

    //mutableTaskDelegatesKeyedByTaskIdentifier 存放着 @{urltask:AFURLSessionManagerTaskDelegate}
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //对象锁 将来会操作 self.mutableTaskDelegatesKeyedByTaskIdentifier对象
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    //清除磁盘和临时网络缓存。不过按理说刚初始化时应该是空的、可能不理解其中深意。
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        NSLog(@"%@",dataTasks);
        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;
}

NSURLRequest生成一个NSURLSessionDataTask对象

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    
    //内部针对不同iOS版本采用了不同的同步策略保证创建的NSURLSessionDataTask不唯一。具体可以到帖子上去看。
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

为每个NSURLSessionDataTask对象生成对应的delegate对象。

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    //搞一个AFURLSessionManagerTaskDelegate对象出来
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    //与本类关联
    delegate.manager = self;
    //设置完成回调---由用户传入
    delegate.completionHandler = completionHandler;
    //添加个描述。具体为self对象的指针str
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    //将代理和task关联
    //task.taskIdentifier 是由session统一分配的标识符
    [self setDelegate:delegate forTask:dataTask];
    //设置下载/上传的进度回调---由用户传入
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

关联task与其对应的delegate.将manager从一些无法绕开的数据处理以及传递中解放出来

//关联task与其对应的delegate
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    //两个断言
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    //线程安全
    [self.lock lock];
    //将delegate 与 task绑定
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    //为AFTaskDelegate设置 task 的进度监听
    [delegate setupProgressForTask:task];
    //为任务添加监听、包括暂停和开始
    //后面还会hook暂停和开始的方法、触发监听
    [self addNotificationObserverForTask:task];
    
    [self.lock unlock];
}
NSURLSession的代理方法

NSURLSession的代理被设置为AFURLSessionManager自身。

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

所有的代理、都由AFURLSessionManager承接。


这里manager实现了NSURLSession的所有代理方法。具体作用我感觉应该单开一篇文章来写、毕竟感觉对理解AFN有很大的用处。
《iOS基础深入补完计划--网络模块NSURLSession概述》

大致分为两种
第一种:由manager自行处理、如果用户手动实现则交给用户处理
//重定向
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    //默认为当前链接
    NSURLRequest *redirectRequest = request;

    if (self.taskWillPerformHTTPRedirection) {
        //如果用户实现了重定向、则采用用户的反馈
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        completionHandler(redirectRequest);
    }
}
第二种:下放给AFURLSessionManagerTaskDelegate处理、如果用户手动实现了则(仅当移动文件时)作废。
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    if (delegate) {
        //我之前一直很好奇为什么AFURLSessionManagerTaskDelegate与session没有绑定delegate也能调用代理方法
        //原来是由manager主动调用的--里面的回调是task级别、也就是创建task时的block
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }
    
    //你也可以手动回去完成信息--这个是session级别的。
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

//服务器成功返回数据
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

//下载任务已经完成
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        //获取用户手动移动的位置
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            if (error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }
            //如果用户自己指定了位置、那就不自动移动文件了
            return;
        }
    }
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

AFURLSessionManagerTaskDelegate

用来承接AFURLSessionManager截获的NSURLSession某些需要特别处理或者说绕不开(进度、任务结束、数据流拼接、下载完成后的文件移动)的代理。

具体它要管理些什么、很凑巧、基本都展现在属性里了:

@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@property (nonatomic, weak) AFURLSessionManager *manager;//弱持有manager 为了在必要的时候获取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

任务进度

当监听属性发生改变。修改delegate对应progress。
当自己的progress发生改变时、调用对应的block反馈给用户。

#pragma mark - NSProgress Tracking
//设置进度观察
- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;
    
    //获取task上传/下载的预期大小
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    
    //可以取消
    [self.uploadProgress setCancellable:YES];
    
    [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属性
    [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];

    //观察进度完成
    [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))];
}

//当总大小、进度等有更新时。将与每个taskDelegate对象绑定的Progress传递出去
//以达成用block控制不同任务Progress代理的需求
- (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);
        }
    }
}

承接方法

任务结束:

//任务结束
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
//忽略系统警告 大概是因为作者下面习惯使用A ?: B这种选择式
#pragma clang diagnostic ignored "-Wgnu"
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    //保存序列化器
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //抛弃了self.mutableData的引用、释放出来一些内存。
        self.mutableData = nil;
    }

    if (self.downloadFileURL) {
        //保存下载文件储存的位置
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        //保存task获取到的原始数据
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        //请求出错
        
        //保存错误信息
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
        // 这里 A ?: B === A ? A : B;
        //如果用户没有定制、则使用AF提供的分组和队列
        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);
            }
            
            //通知
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        //请求成功
        
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            //将数据解析成指定格式
            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;
            }
            //如果用户没有定制、则使用AF提供的分组和队列
            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
}

得到部分数据时:
这里比我们手动实现NSURLSession要人性化得多。
因为AFURLSessionManagerTaskDelegate是跟随任务的、数据容器mutableData自然也是跟随每个任务。不需要每次开启新任务的时候把原来的容器置空。

//服务器返回了(可能是一部分)数据
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //组合数据
    [self.mutableData appendData:data];
}

下载任务完成时:

//下载任务完成
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        //获取用户自定义存储位置
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            //将位于location原始位置的文件移动到自定义位置
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            if (fileManagerError) {
                //如果失败了发个通知
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

以上三个方法、由manager在同名的代理方法中主动调用。
作用显而易见、两个完成的代理、一个记录receive data的代理。
而manager在其中的作用

  • 如果用户实现了对应的block、单纯的将代理中的参数传递给用户。
  • 如果属于上述三个方法、交由AFURLSessionManagerTaskDelegate处理。

AFHTTPSessionManager

AFURLSessionManager针对HTTP请求特化的扩展。

  • 主要体现在Request上
    比如请求方式、请求头请求体的配置等等。
  • 仅向外提供了几个API
    GET、POST、HEAD、PUT以及DELETE
  • 以及三个属性
/**
    请求链接
 */
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

/**
    请求器、负责请求头、请求体等配置
 */
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

/**
    解码器、负责响应解码。比如json格式等等
 */
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

业务流程

初始化

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // 截取掉最后的'/'
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    //请求路径
    self.baseURL = url;
    //请求器
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    //解码器
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

以GET请求举例

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    //所有只需要url和参数的请求都要汇聚于此
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    [dataTask resume];

    return dataTask;
}

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    //生成一个可变请求
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    //这个就回到AFURLSessionManager的原生方法了
    //通过req生成一个数据任务
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            //失败
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            //成功
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

很简单、就是在调用AFURLSessionManager正常的请求方法dataTaskWithRequest:之前、根据不同的请求方式生成了不同的Request、然后帮我们把任务resume启动而已。

分片上传

一个比较特殊的API

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
     constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                      progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    NSError *serializationError = nil;
    //将block的数据以流的形式分片上传
    NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    //这个就回到AFURLSessionManager的原生方法了
    //通过req生成一个分段数据任务
    __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(task, error);
            }
        } else {
            if (success) {
                success(task, responseObject);
            }
        }
    }];

    [task resume];

    return task;
}

里面的主线流程也是一样、唯一不用的是请求Request的生产方法.

NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString]

在用NSURLRequest上传文件时,一般是两种方法:

  • 一个是设置body、但是如果文件稍大的话,将会撑爆内存。

  • 另外一种则是创建一个临时文件、将数据拼接进去、然后将文件路径设置为bodyStream、这样就可以分片的上传了。

  • 而AFN则是更进一步的运用边传边拼的方式上传文件、这无疑是更加高端也是更加繁琐的方法。
    具体由于我现在还没有看到具体模块、有兴趣可以先看一下另一篇帖子《AFNetworking分析<二>》

  • 使用的话、到时可以举个例子

[manager POST:@"http://localhost/demo/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {  
          
        NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"头像1.png" withExtension:nil];  
        
        NSString *fileName = @"fileName"  ;
         
        //AFN会持续的将formData中的数据取出并且转换成不同的AFHTTPBodyPart追加给AFMultipartBodyStream。
        //最后将这个AFMultipartBodyStream赋值给NSMutableURLRequest的bodyStream、在网络请求时交由NSURLConnection读取。
        [formData appendPartWithFileURL:fileURL name:@"uploadFile" fileName:fileName mimeType:@"image/png" error:NULL];  
          
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {  
        NSLog(@"OK");  
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {  
        NSLog(@"error");  
    }];  

一些比较有意思的东西

  • 在监听属性的时候、可以用NSStringFromSelector(@selector(xxx))这种方式来自动提示。

因为属性本身就是与其get方法同名、可以降低出错概率。

[self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
  • 功能AIP分层

AFURLSessionManager实现了所有的NSURLSessionDelegate
但同时又将其中某些需要处理复杂逻辑的代理传递给了AFURLSessionManagerTaskDelegate
使得代码更清晰、逻辑更明确。
需要注意的是、AFURLSessionManagerTaskDelegate完全包裹在了AFURLSessionManager内部、外界完全感受到他的存在。但是又能做数据处理、这个架构设计真心很赞。
除此之外、AFURLSessionManagerAFHTTPSessionManager之间也做了很好的分层。
你可以单独使用AFURLSessionManager进行网络会话、也可以通过AFHTTPSessionManager更好的使用AFURLSessionManager进行HTTP请求。

  • 如何防止block循环引用

其实我几年前就听说AFN可以防止循环引用、但是一直没看。
今天找了找发现似乎已经没有了这段代码
所以个人推测现在不会引起循环引用的原因、应该是因为AFN都在作为单例使用、和self并不互相持有。

贴一段以前别人帖子里的代码:
//复写setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
        __weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //看有没有自定义的完成组,否则用AF的组
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            //看有没有自定义的完成queue,否则用主队列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //调用设置的Block,在这个组和队列中
            dispatch_group_async(group, queue, ^{
                block();
            });

            //结束时候置nil,防止循环引用
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}

  • 把NSURLSession众多代理转化成了block

这个说实话我并不太暂停...
个人感觉block就是应该控制个数、而NSURLSession的代理加起来起码有二三十个。
如果到了这种数量级的数据传递、真的还是用代理吧、饶了我。

  • 消除编译器clang警告

其中Wgnu可以换成其他具体命令

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
  • 正则的简便写法

讲道理我还真第一次见

`A ?: B = A ? A : B`
  • 如何做到对外只读、对内读写
.h中
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
.m中
@property (readwrite, nonatomic, strong) NSURL *baseURL;

API注释Demo

把注释的源码放在了github上、有兴趣可以自取。

GitHub


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。


参考

【原】AFNetworking源码阅读(四)
AFNetworking分析<二>
iOS基础深入补完计划--网络模块NSURLSession概述

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

推荐阅读更多精彩内容