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对应的数据回调出去。

链接://www.greatytc.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,给我们规避了很多问题。

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

推荐阅读更多精彩内容