IOS源码解析:AFNetworking

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、功能模块
  • 二、初始化方法
    • 1、层层调用
    • 2、最终调用的初始化方法
    • 3、父类 AFURLSessionManager 的初始化方法
  • 三、GET请求方法
    • 1、生成 dataTask
    • 2、生成request
    • 3、观察 NSURLRequest 的属性
    • 4、请求序列化
  • 四、NSURLSessionDelegate的实现
    • 1、代理之间的继承关系
    • 2、NSURLSessionDelegate
    • 3、NSURLSessionTaskDelegate
    • 4、NSURLSessionDownloadDelegate
  • Demo
  • 参考文献

作为一个iOS开发者,也许你不知道NSURLRequest、不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking。大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession而选择AFNetworking? 本文将从源码的角度去分析AF的实际作用。

一、功能模块

  • 网络通信模块(AFURLSessionManagerAFHTTPSessionManger)
  • 网络状态监听模块(Reachability)
  • 网络通信安全策略模块(Security)
  • 网络通信信息序列化/反序列化模块(Serialization)
  • 对于iOS UIKit库的扩展(UIKit)
功能模块

核心当然是网络通信模块AFURLSessionManager。AF3.x是基于NSURLSession来封装的。所以这个类围绕着NSURLSession做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包。

其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager或者其它类去做。

这五个模块所对应的类的结构关系图如下所示:

AF架构图

二、初始化方法

AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];

1、层层调用

可以看到,调用了初始化方法生成了一个manager

+ (instancetype)manager 
{
    return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init 
{
    return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url 
{
    return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 
{
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
}

2、最终调用的初始化方法

初始化方法调用父类的初始化方法,其父类也就是AF3.x最最核心的类AFURLSessionManager。除此之外,方法中把baseURL存了起来,还生成了一个请求序列对象和一个响应序列对象。

- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    ...
    return self;
}
❶ 调用父类初始化方法
self = [super initWithSessionConfiguration:configuration];
if (!self)
{
    return nil;
}
❷ url有值且最后不包含/,那么在url的baseurlpath末尾添加/
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"])
{
    url = [url URLByAppendingPathComponent:@"/"];
}
self.baseURL = url;
❸ 给requestSerializer、responseSerializer设置默认值
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];

3、父类 AFURLSessionManager 的初始化方法

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
    .....
    return self;
}
❶ 设置默认的configuration,配置我们的session
if (!configuration)
{
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}

// 持有configuration
self.sessionConfiguration = configuration;
❷ 设置delegate的操作队列并发的线程数量1,也就是串行队列
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
❸ 如果完成后需要做复杂(耗时)的处理,可以选择异步队列。如果完成后直接更新UI,可以选择主队列 [NSOperationQueue mainQueue]
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
❹ 默认为json解析
self.responseSerializer = [AFJSONResponseSerializer serializer];
❺ 设置默认证书为无条件信任证书进行https认证
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
❻ 网络状态监听
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
❼ 设置存储词典。每一个task都会被匹配一个AFURLSessionManagerTaskDelegate来做task的delegate,进行事件处理
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
❽ 使用NSLock确保词典在多线程访问时的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
❾ 置空task关联的代理

异步获取当前session的所有未完成的task。其实讲道理来说在初始化中调用这个方法里面应该一个task都不会有,打断点去看,也确实如此,里面的数组都是空的。但当后台任务重新回来初始化session,可能就会有先前的请求任务,导致程序的crash

[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];
    }
}];

二、GET请求方法

[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    ...
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    ...    
}];

AFHTTPSessionManager类中Get请求方法会调用父类,也就是我们整个AF3.x的核心类AFURLSessionManagerGet请求方法,生成了一个系统的NSURLSessionDataTask实例,并且开始网络请求。

- (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
{
   ...
   return dataTask;
}

1、生成 dataTask

a、在Get方法中生成一个dataTask,然后开始网络请求
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                    URLString:URLString
                                                   parameters:parameters
                                               uploadProgress:nil
                                             downloadProgress:downloadProgress
                                                      success:success
                                                      failure:failure];
[dataTask resume];

b、生成 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
{
    ......
    return dataTask;
}
❶ 先调用AFHTTPRequestSerializer的requestWithMethod函数构建request。relativeToURL表示将URLString拼接到baseURL后面
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
❷ 处理request构建产生的错误。如果解析错误,直接返回
NSError *serializationError = nil;

if (serializationError)
{
    if (failure)
    {
        ...
    }

    return nil;
}
❸ diagnostic是诊断的意思,常与push pop搭配,来忽略一些编译器的警告,这里是用来忽略 ?带来的警告。completionQueue是我们自定义的一个GCD的Queue,如果设置了那么从这个Queue中回调结果,不存在则从主队列回调。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
    failure(nil, serializationError);
});
#pragma clang diagnostic pop
❹ 此时的request已经将参数拼接在url后面,根据request来生成dataTask
__block NSURLSessionDataTask *dataTask = nil;
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);
    }
}

2、生成request

使用指定的HTTP methodURLString来构建一个NSMutableURLRequest对象实例。如果methodGETHEADDELETE,那parameter将会被用来构建一个基于url编码的查询字符串(query url),并且这个字符串会直接加到requesturl后面。对于其他的Method,比如POST/PUT,它们会根据parameterEncoding属性进行编码,而后加到requesthttp body上。

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    ......
    return mutableRequest;
}
❶ 断言:debug模式下,如果该参数为nil,crash并直接打印出来
NSParameterAssert(method);
NSParameterAssert(URLString);
❷ 我们传进来的是一个字符串,在这里它帮你转成url
NSURL *url = [NSURL URLWithString:URLString];
❸ 设置请求方式(get、post、put.....)
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
❹ 将request的各种属性遍历。将观察到发生变化的属性添加到NSMutableURLRequest(如:timeout)。通过KVC动态的给mutableRequest添加value
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
{
    if ([self.mutableObservedChangedKeyPaths containsObject:keyPath])
    {
        [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
    }
}
❺ 将传入的parameters进行编码,拼接到url后并返回
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

3、观察 NSURLRequest 的属性

a、观察属性变化
这个c函数封装了一些NSURLRequest属性相关的方法名并将其作为数组返回
static NSArray * AFHTTPRequestSerializerObservedKeyPaths()
{
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _AFHTTPRequestSerializerObservedKeyPaths =
        @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
    });
    return _AFHTTPRequestSerializerObservedKeyPaths;
}
allowsCellularAccess

是否允许使用设备的蜂窝移动网络来创建request,默认为允许。

cachePolicy

创建的request所使用的缓存策略,默认使用NSURLRequestUseProtocolCachePolicy,该策略表示如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata,则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端。

HTTPShouldHandleCookies

如果设置HTTPShouldHandleCookiesYES,就处理存储在NSHTTPCookieStore中的cookiesHTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去。

HTTPShouldUsePipelining

表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。如果为YES表示必须等receiver收到先前的回复才能发送下个信息。

networkServiceType

设定request的网络服务类型,默认是NSURLNetworkServiceTypeDefault。这个网络服务是为了告诉系统网络层这个request使用的目的,比如NSURLNetworkServiceTypeVoIP表示这个request是用来请求网际协议通话技术(Voice over IP)。系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等,客户端基本不使用。

timeoutInterval:超时机制,默认60秒

b、某个request需要观察的属性集合
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;

-init方法里对这个集合进行了初始化,并且对当前类的和NSURLRequest相关的那些属性添加了KVO监听。

- (instancetype)init
{
    .......
    // 每次都会重置变化
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
    {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)])
        {
            // 给request各种属性的set方法添加观察者
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    .......
}

c、对应的KVO触发的方法

如果KVO的触发机制是默认触发,则返回true,否则返回false。在这里,只要是AFHTTPRequestSerializerObservedKeyPaths里面的属性,我们都取消自动触发kvo机制,使用手动触发。为什么手动,我猜应该是为了在监听这些属性时可以用于某些特殊的操作,比如测试这些属性变化是否崩溃等。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key])
    {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

当观察到这些set方法被调用了,而且不为Null就会添加到集合里,否则移除。

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext)
    {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]])
        {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        }
        else
        {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

4、请求序列化

举个例子演示下整个转码过程。

@{ 
     @"name" : @"bang", 
     @"phone": @{@"mobile": @"xx", @"home": @"xx"}, 
     @"families": @[@"father", @"mother"], 
     @"nums": [NSSet setWithObjects:@"1", @"2", nil] 
} 
-> 
@[ 
     field: @"name", value: @"bang", 
     field: @"phone[mobile]", value: @"xx", 
     field: @"phone[home]", value: @"xx", 
     field: @"families[]", value: @"father", 
     field: @"families[]", value: @"mother", 
     field: @"nums", value: @"1", 
     field: @"nums", value: @"2", 
] 
-> 
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

目的是将原来的容器类型的参数变成字符串类型。

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    ...
    return mutableRequest;
}
a、设置请求方法
  • 请求行(状态行):get、url、http协议
  • 请求头:content-type、accept-language
  • 请求体:get参数拼接在url后面,post数据放在body
❶ 从self.HTTPRequestHeaders里去遍历拿到设置的参数,如果request此项无值则给mutableRequest.headfiled赋值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
    if (![request valueForHTTPHeaderField:field])
    {
        [mutableRequest setValue:value forHTTPHeaderField:field];
    }
}];
❷ 将我们传入的字典转成字符串。至于转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式
NSString *query = nil;
if (parameters)
{
    // 自定义的解析方式
    if (self.queryStringSerialization)
    {
        NSError *serializationError;
        query = self.queryStringSerialization(request, parameters, &serializationError);
    }
    // 默认解析方式 count=5&start=1
    else
    {
        switch (self.queryStringSerializationStyle)
        {
            case AFHTTPRequestQueryStringDefaultStyle:
                // 将parameters传入这个c函数
                query = AFQueryStringFromParameters(parameters);
                break;
        }
    }
}
❸ 最后判断该request中是否包含了GET、HEAD、DELETE,因为这几个method的query是拼接到url后面的,而POST、PUT是把query拼接到http body中的。application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式。
// get等请求
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]])
{
    if (query && query.length > 0)
    {
        mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
    }
}
// post等请求
else
{
    if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"])
    {
        [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }
    // 设置请求体
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

b、默认的解析方式
NSString * AFQueryStringFromParameters(NSDictionary *parameters)
{
    // 一对
    NSMutableArray *mutablePairs = [NSMutableArray array];
    
    // 把参数传给AFQueryStringPairsFromDictionary
    // AFQueryStringPair是数据处理类
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters))
    {
        // 百分号编码
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    // 将拆分数组进行拼接返回参数字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}

c、数据处理类

AFQueryStringPair是一个数据处理类,只有两个属性:fieldvalue。方法:URLEncodedStringValue的作用是以key=value的形式,用百分号编码。

@interface AFQueryStringPair : NSObject

@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (NSString *)URLEncodedStringValue;

@end
- (NSString *)URLEncodedStringValue
{
    if (!self.value || [self.value isEqual:[NSNull null]])
    {
        return AFPercentEscapedStringFromString([self.field description]);
    }
    else
    {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}

d、请求参数对
❶ AFQueryStringPairsFromDictionary只是起过渡作用,往下继续调用。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) 
{
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
❷ 在AFQueryStringPairsFromKeyAndValue中使用了递归,会对value的类型进行判断,有NSDictionary、NSArray、NSSet类型。

不过有人就会问了,在AFQueryStringPairsFromDictionary中给AFQueryStringPairsFromKeyAndValue函数传入的value不是NSDictionary嘛?还要判断那么多类型干啥?对,问得很好,这就是AFQueryStringPairsFromKeyAndValue的核心——递归调用并解析,你不能保证NSDictionaryvalue中存放的不是一个NSArrayNSSet

NSArray *AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
{
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
    ...
    return mutableQueryStringComponents;
}
❸ 根据需要排列的对象的description来进行升序排列。因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数。 比如:@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
❹ 判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array、dictionary、set以外的元素,然后把得到的参数数组返回
if ([value isKindOfClass:[NSDictionary class]])
{
    NSDictionary *dictionary = value;
    
    for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]])
    {
        id nestedValue = dictionary[nestedKey];
        if (nestedValue)
        {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
        }
    }
}
else if ([value isKindOfClass:[NSArray class]])
{
    NSArray *array = value;
    for (id nestedValue in array)
    {
        [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
    }
}
else if ([value isKindOfClass:[NSSet class]])
{
    NSSet *set = value;
    for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]])
    {
        [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
    }
}

既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value是一个NSString,那么就得调用函数中最后的else语句。

else
{
    [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}

四、NSURLSession相关的代理

1、代理之间的继承关系

在第一步探索父类AFURLSessionManager的初始化方法时,有句代码把AFURLSessionManager作为了所有的taskdelegate

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 
{
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    .......
}

这样当我们请求网络的时候,AFUrlSessionManager实现的这一大堆NSURLSession相关的代理开始调用了。

其中有3条重要的,它们转发到了AFURLSessionManagerTaskDelegate即AF自定义的代理,负责把每个task对应的数据回调出去。

@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>

@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>

可以看到这些代理都是继承关系,而在NSURLSession实现中,只要设置了这个代理,它会去判断这些所有的代理是否respondsToSelector这些代理中的方法,如果响应了就会去调用。每个代理方法对应一个我们自定义的Block,如果Block被赋值了,那么就调用它。


2、NSURLSessionDelegate

a、didBecomeInvalidWithError:当前session失效时会调用

如果你使用finishTasksAndInvalidate函数使该session失效,那么session首先会先完成最后一个task,然后再调用URLSession:didBecomeInvalidWithError:代理方法。如果你调用invalidateAndCancel方法来使session失效,那么该session会立即调用这个代理方法。

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid)
    {
        self.sessionDidBecomeInvalid(session, error);
    }
    // 不过源代码中没有举例如何使用这个Notification,所以需要用户自己定义,比如结束进度条的显示
    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

b、didReceiveChallenge:收到服务端的challenge,例如https需要验证证书等

web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDispositionNSURLCredentialdisposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书。最后调用completionHandler回应服务器端的挑战。

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    ...
}
❶ 设置挑战处理类型为默认
  • NSURLSessionAuthChallengeUseCredential:使用指定的证书
  • NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
  • NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
  • NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
❷ 自定义方法,用来应对服务器端的认证挑战
__block NSURLCredential *credential = nil;// 证书
if (self.sessionDidReceiveAuthenticationChallenge)
{
    disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
❸ 倘若没有自定义Block
//  判断接收服务器挑战的方法是否是信任证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
    // 只需要验证服务端证书是否安全,即https的单向认证,这是AF默认处理的认证方式
    if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
    {
         // 信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务器,我信任你,你给我发送数据吧
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        // 确定挑战的方式
        if (credential)
        {
            disposition = NSURLSessionAuthChallengeUseCredential;// 证书挑战
        }
        else
        {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;// 默认挑战
        }
    }
    else
    {
        disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;// 取消挑战
    }
}
// 默认挑战方式
else
{
    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
❹ 完成挑战,将信任凭证发送给服务端
if (completionHandler)
{
    completionHandler(disposition, credential);
}

4、NSURLSessionDownloadDelegate

a、didFinishDownloadingToURL
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    // 这个是session也就是全局的,后面的个人代理也会做同样的这件事
    if (self.downloadTaskDidFinishDownloading)
    {
        // 自定义函数,根据从服务器端获取到的数据临时地址location等参数构建出你想要将临时文件移动的位置
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        
        if (fileURL)
        {
            // 如果fileURL存在的话,表示用户希望把临时数据存起来
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            
            // 将位于location位置的文件全部移到fileURL位置
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            
            // 如果移动文件失败,就发送AFURLSessionDownloadTaskDidFailToMoveFileNotification
            if (error)
            {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }
    
    // 转发代理:这一步比较诡异,感觉有重复的嫌疑
    if (delegate)
    {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

b、didFinishDownloadingToURL:周期性地通知下载进度调用
  • bytesWritten:表示自上次调用该方法后,接收到的数据字节数
  • totalBytesWritten:表示目前已经接收到的数据字节数
  • totalBytesExpectedToWrite:表示期望收到的文件总字节数
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    if (self.downloadTaskDidWriteData)
    {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

c、didResumeAtOffset:下载任务重新开始下载

如果一个resumable下载任务被取消或者失败了,你可以请求一个resumeData对象(比如在userInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取到resumeData),并使用它来提供足够的信息以重新开始下载任务。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    if (self.downloadTaskDidResume)
    {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

Demo

Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo

参考文献

AFNetworking到底做了什么?

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

推荐阅读更多精彩内容