学习AFN

说实话,AFN是个博大精深的东西,作为一款全世界都在使用的iOS框架想要一下子就参透也是不大可能的,但是,我还是要契而不舍的钻研他,参透他!

1.AFN的使用

要研究一款框架的实现,首先要了解这个框架的基本使用,在开发中我们用到AFN主要就是在发送网络请求、下载数据、上传数据。

用AFN可以非常方便的发送get请求和post请求,方法相同,只是需要修改一下参数和函数名。

AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];

//我们也可以把这部分加粗的参数存放在一个字典里,然后通过parameters这个参数传递。

[manager GET:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {子线程执行

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

        //task.response是请求头

        NSLog(@"%@",responseObject);

        NSLog(@"success");主线程执行

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

        NSLog(@"failure");主线程执行

    }];

post方法也是一样的,只不过把请求的参数存放到一个字典里传递。

下载文件的方法:

-(void)download{

    AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];

    NSURL* url = [[NSURL alloc] initWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];

    NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url];

    NSURLSessionDownloadTask* task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {         NSLog(@"%f",1.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);⚠️:这里用downloadProgress自带的两个参数相除就可以得到下载进度,在2.x版本的时候没有这个回调参数,需要使用kvo监听下载进度的改变。

    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {

⚠️:这个block是有返回值的,需要返回一个NSURL,在这个block块里我们需要把下载到的文件从临时路径剪切到指定的目标路径。targetPath就是AFN为我们自动下载到的临时路径,我们需要提供一个目标路径filePathAFN会自动把下载下来的文件从临时文件夹剪切到filePath路径下

        NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:response.suggestedFilename];

        NSLog(@"1-----%@",filePath);

        NSLog(@"2-----%@",[NSThread currentThread]);//这段代码是在子线程执行的

        return [NSURL fileURLWithPath:filePath];

    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {

        NSLog(@"download finish");

        NSLog(@"2-----%@",filePath);

        NSLog(@"2-----%@",[NSThread currentThread]);主线程执行

    }];

    [task resume];需要手动开启执行任务

}

上传文件的方法:

-(void)upload{

        AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];                          

⚠️:这里的上传文件的方法不是什么什么upload,而是一种post方法

        [manager POST:@"http://120.25.226.186:32812/upload" parameters:nil constructingBodyWithBlock:^(id _Nonnull formData) {

        [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"/Users/apple/Documents/我自己 2.jpg"] name:@"my" error:nil];

    } progress:^(NSProgress * _Nonnull uploadProgress) {        NSLog(@"%f",1.0*uploadProgress.completedUnitCount/uploadProgress.totalUnitCount);//上传的操作是开启子线程并发执行的

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

        NSLog(@"success");//下载成功的回调在主线程执行

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

        NSLog(@"fail");//下载失败的回调在主线程执行

    }];

}

2.框架的结构

AFNetworking主要分为五个模块:

网络通信模块(AFURLSessionManager、AFHTTPSessionManger)

网络状态监听模块(Reachability)

网络通信安全策略模块(Security)

网络通信信息序列化/反序列化模块(Serialization)

对于iOS UIKit库的扩展(UIKit)

3.对不同类型数据的解析

如果我们请求的数据是json类型的,那么正确返回的数据也是json类型的,这个时候我们不需要修改数据的解析方案,直接请求就可以了。

如果我们请求的数据是xml类型的,这个时候就需要修改数据的解析方案了。

-(void)getXML{

AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];

⚠️:设置解析方法:manager.responseSerializer = [AFXMLParserResponseSerializer serializer];

[manager GET:@"http://120.25.226.186:32812/login2?username=123&pwd=123&type=XML" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {            `            NSLog(@"%f",1.0*downloadProgress.completedUnitCount/downloadProgress.totalUnitCount); }

success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //这个时候的responseObject是NSXMLParser类型的,无法直接拿到数据 NSXMLParser* parser = (NSXMLParser*)responseObject;把responseObject强制转化为NSXMLParser类型的对象,之后用代理方法进行解析。

parser.delegate = self;

[parser parse]; }

failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"fail---%@",error); }];}

//用代理方法解析数据

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{

    NSLog(@"%@---%@",elementName,attributeDict);

}

如果请求到的数据既不是json类型的,也不是xml类型的,这个时候就需要使用这种解析方法:

manager.responseSerializer = [AFHTTPResponseSerializer serializer];

请求到的responseObject是NSData类型的,可以对他进行下一步的转化。其实这种方法是默认的,可以解析所有类型的数据。

4.为什么要用AFN框架

说到为什么要用这个框架,我们就必须要知道在没有使用这个框架之前用iOS原生的机制来处理网络请求会发生什么问题。

首先我们来说最原始的NSURLConnection是怎么发送网络请求的。

发送网络请求的方法可以分为两类,异步发送请求和同步发送请求。

异步发送网络请求有两种方法,一种是sendAsynchronousRequest,一种是用delegate。

同步发送网络请求用sendSynchronousRequest。

如果是使用sendSynchronousRequest或者sendAsynchronousRequest方法,我们是用block来处理回调的,可以指定NSOperationQueue指定回调方法在哪个队列执行。

如果是在主线程用代理方法发送网络请求,那么代理方法也是在主线程执行的,如果要让代理方法在子线程执行可以开子线程发送网络请求,或者设置setDelegateQueue。

那么只要是遇到了发送网络请求的部分,我们就需要让UIViewController去遵守协议,实现代理方法。这样做的坏处就是,网络层和控制器写在了一块儿,没有剥离开,也没有实现网络请求的统一管理。

基于NSURLConnection的AFN做了什么呢?

就是把网络请求和回调进行了统一的管理,不需要为每一个请求开启子线程,这里的做法是开启一条常驻子线程处理所有的请求和响应。

那么NSURLSession对比起NSURLConnection有哪些不同呢?

参考文章:从 NSURLConnection 到 NSURLSession

❓首先我们讨论一个问题,就是使用NSURLConnection容易造成什么问题?

NSURLConnectoin只隐藏了单个网络请求的线程的相关操作,并没有提供接口来解决多个网络请求时多个线程的管理问题。

并且NSURLConnection不是基于HTTP/2协议的,若使用NSURLConnection发起请求则每次请求都需要经过三次握手过程。而使用了session之后,使用同一个session中的task访问数据,不用每次都实现三次握手,复用之前的连接可以加快访问速度。

5.源码分析

这里有一篇非常特别的源码分析,他不是在分析AFN这个源码的实现流程,而是总结了自己在学习这个源码的过程中学到了哪些思想。

AFNetworking 3.0 源码解读 总结(干货)(上)

重要的方法1:AFURLSessionManager的initWithSessionConfiguration:方法

    self.sessionConfiguration = configuration;

    self.operationQueue = [[NSOperationQueue alloc] init];

    self.operationQueue.maxConcurrentOperationCount = 1;

    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];⚠️:这里设置的代理操作队列的最大并发数为1,为了让所有的请求的发起和等待网络响应都在同一个线程,不需要为每一个请求创建一个线程。

self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy];

使用代理方法完成进一步的操作。

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];在这个方法里,给dataTask添加了一个代理,这个代理负责处理后续的数据拼接和数据操作。在实现delegateForTask:这个方法的时候,上锁,并给delegate一个唯一的标识,防止不同的task使用同一个delegate。

    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    if (self.dataTaskDidReceiveData) {

        self.dataTaskDidReceiveData(session, dataTask, data);

    }

}

数据传输完成后,调用didCompleteWithError:(NSError *)error方法:

- (void)URLSession:(NSURLSession *)session  task:(NSURLSessionTask *)task

didCompleteWithError:(NSError *)error

{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background

    if (delegate) {

        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];把task对应的代理从代理字典里移除

    }

    if (self.taskDidComplete) {

        self.taskDidComplete(session, task, error);

    }

}

⚠️:AFN中使用了两个操作队列,一条是在AFURLSessionManager里的初始化方法里创建的最大并发数为1的NSOperationQueue,用来处理所有的网络请求和等待响应。数据的解析在一个异步并发的操作队列里执行。

同时多个网络请求直接多次调用AFHTTPSessionManager的GET方法就行了。一个请求依赖另一个请求的结果,在第一个请求的成功或失败回调中发起第二个请求就是最好的方法。

参考文章:AFNetworking源码分析

AFN中的两种代理:

有三个代理方法转发到了AFN的代理中,这三个方法里的代理是需要对应每个task做私有化处理的。

其他的方法都试针对这个sessionManager所有的request的,是公用的处理。

三个方法分别是:didCompleteWithError、didReceiveData、didFinishDownloadingToURL

6.AFN中图片的解压

首先我们要知道为什么要对图片解压,我们下载到的JPG,PNG类型的图片是不能直接用来显示的。当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压转成bitmap后才能渲染到屏幕上,如果不做任何处理,当你把UIImage赋给UIImageView,在渲染之前底层会判断到UIImage对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操作,再渲染到屏幕上。这个解压操作是比较耗时的,如果任由它在主线程做,可能会导致速度慢UI卡顿的问题。

7.网络请求中的缓存

网络请求的缓存和我们经常用到的图片的缓存,数据的缓存其实是一个木目的,就是为了加快请求的响应时间,避免重复发送相同的请求,同时提升离线或低网速情况下的用户体验。

当一个请求完成下载来自服务器的回应,一个缓存的回应将在本地保存。下一次同一个请求再发起时,本地保存的回应就会马上返回,不需要连接服务器。

AFN中的缓存机制是通过NSCache来实现的。

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

推荐阅读更多精彩内容