AFNetworking框架详解

AFHTTPSessionManager

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

    // 设置初始化NSURLSessionConfiguration
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    // 设置操作队列及控制最大并发数
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;

    // 设置NSURLSession
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    // 设置默认最后解析接口返回数据类(用什么方式去解析)
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    // 设置一些服务器认证请求
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

    // 实时监控当前网络状况
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

    // 加锁
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    // 这是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。
    // 将所有回调清nil
    [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;
}

- (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:@""];
    }

    // 设置baseURL
    self.baseURL = url;

    // 请求序列化类
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    // 响应序列化类
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

AFHTTPSessionManager继承自AFURLSessionManager类,我们可以通过[AFHTTPSessionManager manager]生成单例对象,会调用initWithBaseURL初始化生成,在生成的初始化对象中设置了响应及请求序列类、会话类NSURLSession及服务器请求认证类!

  • 请求方法
// GET
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      headers:(nullable NSDictionary  *)headers
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;

// HEAD
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
                    parameters:(id)parameters
                       headers:(NSDictionary *)headers
                       success:(void (^)(NSURLSessionDataTask * _Nonnull))success
                       failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;

// POST
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary  *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

// PUT
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
                   parameters:(id)parameters
                      headers:(NSDictionary *)headers
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;

// PATCH
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
                     parameters:(id)parameters
                        headers:(NSDictionary *)headers
                        success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                        failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;

// DELETE
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
                      parameters:(id)parameters
                         headers:(NSDictionary *)headers
                         success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                         failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;

这是AFN中可用于进行网络请求方法,我们可根据需要选择相应的请求方法:

  1. GET:用于获取服务器上的数据,可将参数拼接在请求URL后面,上传参数有限制,默认为1024;
  2. HEAD:用于获取服务器响应首部,不包含实体部分,方便用于根据服务器响应字段,更好地在对请求方式首部进行设置,更精确得去获取到自己所需要的数据;
  3. POST:用于获取服务器上的数据,可将参数放在请求BODY中,参数个数无限制,POST相对应GET请求安全一点;
  4. PUT:通常用于向服务器发送请求,如果URL不存在,则要求服务器根据请求创建资源,如果存在,服务器就接受请求内容,并修改URL资源的原始内容;
  5. PATCH:请求服务器对资源进行局部更新,与PUT方法的区别是,PATCH可用于更新局部资源或字段,而PUT则是更新整个资源文件,要求PUT资源一定是完整的;
  6. DELETE: 请求服务器删除指定的资源;
  • 从简单GET开始
// 开启GET方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      headers:(nullable NSDictionary  *)headers
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                          headers:headers
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    
    [dataTask resume];
    
    return dataTask;
}

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                         headers:(NSDictionary  *)headers
                                  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;
    // 设置拼接请求参数body和首部字段
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    for (NSString *headerField in headers.
         keyEnumerator) {
        // 添加客户端设置的请求首部字段参数
        [request addValue:headers[headerField] forHTTPHeaderField:headerField];
    }
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    // 通过request开启任务
    __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);
            }
        }
    }];

    return dataTask;
}

通过设置请求对象Request来设置请求首部字段及请求参数,并通过dataTaskWithHTTPMethod生成任务对象task并开启任务,让我们继续来一步步研究!

  • 请求AFURLRequestSerialization
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    // 设置请求方式
    mutableRequest.HTTPMethod = method;

    // 通过监听用户mutableRequest里的属性值是否改变,若有则将该属性及属性值设置给mutableRequest
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    // 设置请求参数字段
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

	return mutableRequest;
}

// 设置请求首部及请求参数字段数据body
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    // 用户通过当前类添加需要设置的首部属性值,在该类中是将属性值加到self.HTTPRequestHeaders中的
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) { // 若首部中未包含此属性则添加该属性到该请求的首部HTTPHeader中
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    if (parameters) {
        if (self.queryStringSerialization) { // 用户自定义 实现拼接参数方法
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);
        } else {
            switch (self.queryStringSerializationStyle) { // 默认样式
                case AFHTTPRequestQueryStringDefaultStyle:
                    // 默认样式进行将字典转换后的参数拼接字符串
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    // self.HTTPMethodsEncodingParametersInURI包含@"GET", @"HEAD", @"DELETE",使用拼接在url后面
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            //URL.query判断是否已经有数据参数,若有则直接用&拼接,否则用?拼接
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            // POST请求设置body的默认编码方式
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        
        // 用NSUTF8StringEncoding编码格式将query参数字符串转为NSData
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}
  1. 手动监听mutableRequest中网络请求属性的最新值,若出现了改变,则实时更新并设置到mutableRequest中;
  2. 将用户设置的请求首部设置给mutableRequest的HTTPHeaderField;
  3. 将请求参数进行拼接设置给mutableRequest的HTTPBody;

通过对mutableRequest的相关属性设置参数值后,将返回的mutableRequest生成任务Task并开启任务,此时一个请求任务启动,等待网络请求结果!

  • 请求回调代理
- (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
{
    // 将delegate与manage之间进行关联
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // 数据请求完成时回调AFURLSessionManagerTaskDelegate的代理方法
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

// AFURLSessionManagerTaskDelegate的代理方法
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    __strong AFURLSessionManager *manager = self.manager;

    // 省略部分代码

    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);
            }

        });
    } else { // 未出错时的处理
        dispatch_async(url_session_manager_processing_queue(), ^{

            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

    }
}

完成数据请求时,会回调didCompleteWithError对数据进行相关处理,这里若manager设置了跟AFURLSessionManagerTaskDelegate关联起来,则在任务完成回调后,将调用AFURLSessionManagerTaskDelegate类中的方法对数据进行处理,数据处理的方式按照responseSerializer对应设置的类型方式进行!

  • 响应处理AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{

    // 省略部分代码
    for (id  serializer in self.responseSerializers) {

        NSError *serializerError = nil;
        id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
        if (responseObject) {
            return responseObject;
        }
    }

    return [super responseObjectForResponse:response data:data error:error];
}

继承自AFURLResponseSerialization生成了AFJSONResponseSerializer、AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer、AFImageResponseSerializer和AFCompoundResponseSerializer类,通过生成不同的serializer类对象,有不同的数据处理方式!

 

AFSecurityPolicy安全认证类

随着互联网的快速发展,导致人们的隐私也变得越来越重要了!苹果爸爸一直都很注重用户隐私问题,所以也提倡应更缜密地传输用户数据信息,防止数据泄露!所以在这里也引申出了HTTPS这个概念,HTTPS具体原理可以参考我写的文章 HTTPS协议:叫个外卖咋这么复杂呢!!

  • 苹果认证过程
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        // 自定义处理证书方式
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 判断服务器返回的证书是否是服务器信任的
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { // 通过认证
                // 若验证通过,生成用NSURLCredential类生成证书
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    // NSURLSessionAuthChallengeUseCredential为使用正式
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    // 无证书时,忽略证书,这是系统默认的做法
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else { // 证书未通过认证,直接取消认证,忽略证书
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            // 忽略证书,系统默认处理方式
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        // 使用completionHandler回调,让系统去对接完成服务器端的认证过程
        completionHandler(disposition, credential);
    }
}

实现HTTPS认证过程中,我们可以使用证书或非证书认证的方式,这个具体看公司需求,下面我们来看看这个具体过程!

CA机构认证证书:这种方式可以让客户端开发感觉很爽,只需要将请求URL中的http换成https后,便可以放心地进行数据传输,因为服务器端只需要将用于认证的加密证书配置为CA机构信任认证的证书即可,这样服务器在第一次加密认证时,用认证证书加密服务器公钥发送给客户端时,当加密数据到达客户端后,客户端自带的CA机构证书会认证服务器加密时使用的加密证书,若验证成功后,客户端便可取出服务器用于客户端传输数据到服务器所使用的公钥!

非CA机构认证证书:这个过程一般是使用服务器端配置的证书,而这个证书并非CA机构认证的,但我们可以先将证书给客户端,让客户端去认可这个配置的证书,完成后面的HTTPS验证过程,若验证通过则双方可进行交换数据!

  • 认证过程
  1. 当客户端需要对服务器发送到的证书进行认证时,会调用didReceiveChallenge代理方法;
  2. 若证书是否是服务器所信任的,则通过三种认证模式其中的一种进行认证:

AFSSLPinningModeNone:验证是否允许非权威证书(自签名)或是认证机构信任的证书,是的话则验证通过;

AFSSLPinningModeCertificate:获取客户端证书链中是否有证书包含服务器端证书链中的证书,是的话则验证通过;

AFSSLPinningModePublicKey:从客户端证书链中将证书转换为公钥key,服务器端证书链也一样转换成公钥key,通过各自的公钥key进行判断,若相等则通过验证;

    3.  若证书验证通过,则生成NSURLCredential证书,使用completionHandler回调,让系统去对接完成服务器端的认证过程;

 

AFNetworkReachabilityManager实时监控

AFNetworkReachabilityManager是AFN提供的可以对网络状态进行检测,并在主线程完成监控状态的回调!苹果对需要联网的应用要求很高,就是必须要进行联网检查。另外,当网络发生异常时能够及时提示用户网络已断开,而不是程序问题造成卡顿;

[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        // 一共有四种状态
        switch (status) {
            case AFNetworkReachabilityStatusNotReachable:
                NSLog(@"AFNetworkReachability Not Reachable");
                break;
            case AFNetworkReachabilityStatusReachableViaWWAN:
                NSLog(@"AFNetworkReachability Reachable is WWAN");
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"AFNetworkReachability Reachable is WiFi");
                break;
            case AFNetworkReachabilityStatusUnknown:
            default:
                NSLog(@"AFNetworkReachability Unknown");
                break;
        }
    }];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];

当我们使用单例sharedManager简单地使用startMonitoring开启网络检测,可以实时检测到网络状态,但是存在一点不足之处:

但是这里并不能检测到服务器是否真的可达,只能检测设备是否连接到局域网,以及用的WiFi还是WWAN。即使把设备网络关了,立马检测出NotReachable,连接到路由器立马检测出的还是可以连接到WIFI !有时候虽然联网了,但是网络不一定就能上网是一种可能性,另一种可能性是数据包在传输过程中可以会受影响而被丢弃,毕竟数据包在网际层传输,每经过一个路由器默认经过一跳,但达到255跳时,路由器就会自动丢包!

 

其它

  • 字符串中含有特殊字符进行转码
/**  将字符串中包含的特殊字符进行转码  **/
NSString * AFPercentEscapedStringFromString(NSString *string) {
    
    // 允许特殊字符转码
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    // URL转码合集
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    // 在合集中去掉上面的特殊字符(即使没有也执行此移除操作),相当于允许上面的特殊字符可转码
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

	// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
    // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as ????
        // 将表情或字符等完整截取
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        // 将特殊字符进行转码
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }

	return escaped;
}

 

总结

  1. 以上是AFN的解析,通过代码和原理一步步讲解,AFN是一个很?的框架,基本上现在苹果开发网络请求都是使用这个框架,更多细节方面望读者进行深究,方能掌握网络请求的流程及细节之处!!
  2. 有时候研究一个框架从最简单的方法入手,可以更快地去理解,当然这个过程也少不了实践过程!!

 

实现手动监听KVO

 

你可能感兴趣的:(第三方框架解读)