美文网首页
2022-11-13

2022-11-13

作者: 我妈说我是做工程师的料 | 来源:发表于2022-11-22 16:26 被阅读0次

    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,给我们规避了很多问题。

    相关文章

      网友评论

          本文标题:2022-11-13

          本文链接:https://www.haomeiwen.com/subject/bjyhxdtx.html