AFNetworking详解

AFNetWorking几乎是iOS开发中最为广泛被使用的一个三方开源组件,主要帮助开发者实现和管理iOS项目中的http/https网络请求。目前已经发展到4.0版本

下面简单描述下各个版本的差异:

AFNetworking 1.0 是基于NSURLConnection开发出来的。而NSURLConnection是苹果早些年提供的网络通讯的API接口。目前该接口已经废弃。

AFNetworking 2.0 是基于部分NSURLConnection接口 和部分NSURLSession接口开发的。简单来说,2.0是介于NSURLConnection和NSURLSession的过渡阶段。其中NSURLSession接口是苹果提供且目前主推的网络通讯API接口。

AFNetworking 3.0 完全基于NSURLSession开发,此版本中的NSURLConnection全部弃用。这样不仅降低了代码维护工作,还更好地支持了NSURLSession提供的额外功能。

AFNetworking 4.0 是2020年发布的。主要是配合苹果公司弃用UIWebView控件的升级,同时也移除之前弃用的API接口。不过要特别说明,这个版本支持的iOS版是9.0(之前是7.0),macOS 10.10。

通过以上AFNetWorking的版本演变历史,我们也可以看到iOS网络接口相关类库的演变,从最初的NSURLConnection、到后来的NSURLSession,以及后来抛弃UIWebView,使用WKWebView等等变化。这里需要说明的一点AFNetworking只面向Object-C语言,如果需要使用swift语言的话,可以使用Alamofire库或者原生。


1、抛开AFNetworking直接使用原生是否可以实现网络接口

2、如果原生可以实现,为什么要引入AFNetworking

3、AFNetworking在原生的基础上做了哪些事情


使用原生发送一个接口

NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

    NSString*urlString = [[NSStringalloc]initWithFormat:@"https://www.baidu.com"];

    NSURL*url = [[NSURLalloc]initWithString:urlString];

    NSURLRequest*baiduRequest = [[NSURLRequestalloc]initWithURL:url];

    NSURLSessionTask*task = [sessiondataTaskWithRequest:baiduRequestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror) {

        NSLog([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    }];

    [taskresume];


之前初始化session的时候大家看到了,为session设置了一个delegate和delegate消息响应的线程。这里要说明一下,如果初始化一个task时,使用了下面的这种简易方法,传入了一个handler来处理返回消息,那么该task就不会再调用session对象的delegate方法,使用这里传入的block代替了delegate方法。但是session对象设置的代理响应的线程仍然起作用,比如上面我初始化session的时候,传入的delegateQueue参数是主线程,那么这里handler这个block返回时也是在主线程中。

/*初始化一个session对象,其中三个参数分别是

    *configuration:配置对象

    *delegate:session处理代理的对象

    *delegateQueue:代理的消息处理的线程,这里传mainQueue,代理的消息都会在主线程中收到

    */

    self.curSession = [NSURLSession sessionWithConfiguration:yConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];


使用AFNetworking发送一个接口

static NSString * const AFAppDotNetAPIBaseURLString = @"https://api.app.net/";

    AFHTTPSessionManager *sharedClient = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]];

        sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];

[sharedClient GET:@"stream/0/posts/stream/global"parameters:nilheaders:nilprogress:nilsuccess:^(NSURLSessionDataTask *__unusedtask,idJSON) {

        NSArray *postsFromResponse = [JSON valueForKeyPath:@"data"];

        NSMutableArray *mutablePosts = [NSMutableArray arrayWithCapacity:[postsFromResponse count]];

        for(NSDictionary *attributesinpostsFromResponse) {

            Post *post = [[Post alloc] initWithAttributes:attributes];

            [mutablePosts addObject:post];

        }

        if(block) {

            block([NSArray arrayWithArray:mutablePosts],nil);

        }

    } failure:^(NSURLSessionDataTask *__unusedtask, NSError *error) {

        if(block) {

            block([NSArray array], error);

        }

    }];



1、构建request

NSMutableURLRequest*request = [self.requestSerializerrequestWithMethod:methodURLString:[[NSURLURLWithString:URLStringrelativeToURL:self.baseURL]absoluteString]parameters:parameterserror:&serializationError];

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method

                                URLString:(NSString*)URLString

                                parameters:(id)parameters

                                    error:(NSError*__autoreleasing*)error

{

    NSParameterAssert(method);

    NSParameterAssert(URLString);

    NSURL*url = [NSURLURLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];

    mutableRequest.HTTPMethod= method;

    for (NSString *keyPath in self.mutableObservedChangedKeyPaths) {

        [mutableRequestsetValue:[selfvalueForKeyPath:keyPath]forKey:keyPath];

    }

    mutableRequest = [[selfrequestBySerializingRequest:mutableRequestwithParameters:parameterserror:error]mutableCopy];

returnmutableRequest;

}


@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;


self.mutableObservedChangedKeyPaths = [NSMutableSet set];

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {

        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {

            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];

        }

    }


static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {

    staticNSArray*_AFHTTPRequestSerializerObservedKeyPaths =nil;

    staticdispatch_once_tonceToken;

    dispatch_once(&onceToken, ^{

        _AFHTTPRequestSerializerObservedKeyPaths =@[NSStringFromSelector(@selector(allowsCellularAccess)),NSStringFromSelector(@selector(cachePolicy)),NSStringFromSelector(@selector(HTTPShouldHandleCookies)),NSStringFromSelector(@selector(HTTPShouldUsePipelining)),NSStringFromSelector(@selector(networkServiceType)),NSStringFromSelector(@selector(timeoutInterval))];

    });

    return_AFHTTPRequestSerializerObservedKeyPaths;

}

参数构建,request参数设置,KVO,KVC讲解,用的巧妙,反省我自己写的话大概率就是做一个类,类中有很多属性,属性初始化的时候是null,设置了值之后,在每次创建request的时候,久判断哪个不是null就设置哪个。

同时大概讲解一下request中那些参数的意义。

allowsCellularAccess:(BOOL)allowsCellularAccess

返回值: YES蜂窝数据可用,NO蜂窝数据不可用。

cachePolicy:请求使用的缓存策略

(BOOL)HTTPShouldHandleCookies

返回值: YES 使用默认cookie处理,NO不使用。

默认值为YES。

(NSURLRequestNetworkServiceType)networkServiceType

返回值: 网络服务类型。

网络服务类型给操作系统提示底层通信的作用。这个提示有助于系统优化通信,确定唤醒蜂窝数据或者WIFI的速度。调节不同的参数,可以平衡电池、性能以及其他因素。

比如,进行非用户请求的下载时应该使用 NSURLNetworkServiceTypeBackground。 比如,在后台提前加载数据,这样等用户需要看时就不需要加载了。

(NSTimeInterval)timeoutInterval

返回值: 请求的超时时间,单位秒。


2、多线程相关处理

[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response,NSData *data,NSError *connectionError) {// 有的时候,服务器访问正常,但是会没有数据!// 以下的 if 是比较标准的错误 处理代码!if (connectionError !=nil || data ==nil) {//给用户的提示信息NSLog(@"网络不给力");return;

    }

}];

底层发送使用socket接口,这块我们认为NSURLConnection与NSURLSession一致

maxConcurrentOperationCount =1的设置

多线程回调的历史问题

3、众多代理的处理和回调

我们把AFUrlSessionManager作为了所有的task的delegate。当我们请求网络的时候,这些代理开始调用了:


AFUrlSessionManager一共实现了如上图所示这么一大堆NSUrlSession相关的代理。(小伙伴们的顺序可能不一样,楼主根据代理隶属重新排序了一下)

而只转发了其中3条到AF自定义的delegate中:


这就是我们一开始说的,AFUrlSessionManager对这一大堆代理做了一些公共的处理,而转发到AF自定义代理的3条,则负责把每个task对应的数据回调出去。

链接:https://www.jianshu.com/p/856f0e26279d

4、证书校验

- (void)URLSession:(NSURLSession*)session

              task:(NSURLSessionTask*)task

didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge

completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*credential))completionHandler

{

    BOOLevaluateServerTrust =NO;

    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

    NSURLCredential*credential =nil;

    if (self.authenticationChallengeHandler) {

        idresult =self.authenticationChallengeHandler(session, task, challenge, completionHandler);

        if(result ==nil) {

            return;

        }elseif([resultisKindOfClass:NSError.class]) {

            objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN);

            disposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;

        }else if ([result isKindOfClass:NSURLCredential.class]) {

            credential = result;

            disposition =NSURLSessionAuthChallengeUseCredential;

        }elseif([resultisKindOfClass:NSNumber.class]) {

            disposition = [resultintegerValue];

            NSAssert(disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace, @"");

            evaluateServerTrust = disposition ==NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];

        }else{

            @throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil];

        }

    }else{

        evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];

    }

    if(evaluateServerTrust) {

        if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {

            disposition =NSURLSessionAuthChallengeUseCredential;

            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];

        }else{

            objc_setAssociatedObject(task, AuthenticationChallengeErrorKey,

                                    [selfserverTrustErrorForServerTrust:challenge.protectionSpace.serverTrusturl:task.currentRequest.URL],

                                    OBJC_ASSOCIATION_RETAIN);

            disposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;

        }

    }

    if(completionHandler) {

        completionHandler(disposition, credential);

    }

}

NSURLSessionConfiguration 安全相关的策略

5、返回数据处理

整理回顾,总结AF到底在原生基础上做了哪几件事情

1、2、3、4、5、

总结什么情况下用AF,什么情况下用原生

比如之前说的回调线程最大并发设置为1,如果我们有这种不为1的要求,我们就可以自己使用原生开发,但是大部分情况下让回调并发为1,给我们规避了很多问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容