AFNetworking 2.5

使用

AFNetworking有2套用于网络操作的API:

  • 基于NSURLConnection
  • 基于NSURLSession,要求iOS 7以上版本

通过配置CocoaPods subspecs可挑选需要的模块而无需使用整个AFNetworking。

本文讲解基于NSURLSession的新接口。一个返回数据为JSON格式的HTTP GET请求,最简单的编程步骤为:

  1. 创建AFHTTPSessionManager,如AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]
  2. 调用manager的GET:parameters:success:failure:方法进行GET操作,POST、PUT、DELETE操作类似。

    • GET:请求的URL字符串,如@"http://www.mySite.com/news"http://不可省略
    • parameters:提交给服务器的参数字典,如@{@"time": @"2015-3-22"}
    • success:请求成功后调用的block,接受两个参数:表示请求的task及服务器返回的响应 responseObject,响应的数据类型在默认情况下被AFURLResponseSerialization解析成Foundation数据类型,如NSDictionary
    • failure:请求失败或服务器响应数据解析失败时调用的block,接受两个参数:表示请求的task及错误信息error,可读取error中描述,以便定位问题。

需要说明的是,在[AFHTTPSessionManager manager]的内部实现中,AFNetworking使用了[NSURLSessionConfiguration defaultSessionConfiguration]配置它,AFNetworking源码如下

// AFURLSessionManager.m
------------------------------
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
	//...
	if (!configuration) {
		configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
	}
	//...
}

若要对NSURLSessionConfiguration作更多配置,则需创建NSURLSessionConfiguration的实例对象并配置,然后通过initWithBaseURL:sessionConfiguration:方式创建AFHTTPSessionManager实例,因为AFHTTPSessionManager的SessionConfiguration是只读属性,以[AFHTTPSessionManager manager]快速创建语法得到的对象在正常情况下不可修改其sessionConfiguration属性,使用运行时方式修改则另当别论。

下面给出创建NSURLSessionConfiguration并配置HTTP头的示例代码。

NSURLSessionConfiguration *conf = [NSURLSessionConfiguration defaultSessionConfiguration];
conf.HTTPAdditionalHeaders = @{@"MyHeader": @"MyHeaderValue"};
[[AFHTTPSessionManager alloc] initWithBaseURL:url sessionConfiguration:conf]

在应用中,若都访问同一个基站点,如@"http://www.mySite.com/service/",则建议设置BaseURL值,则后续的POST、GET操作只需加上file部分,如访问http://www.mySite.com/service/menus

[manager POST:@"menus" parameters:@{@"tableName": @"vip-x"} success:nil failure:nil];

若服务器返回的响应数据不是JSON,则需要配置响应序列化器,默认情况下SessionManager的responseSerializer属性值为AFJSONResponseSerializer,且当Content-Type为"application/json"、"text/json"、"text/javascript"时,AFNetworking才解析JSON数据。即便服务器端返回的数据为JSON数据,但是Content-Type是"text/plain",它也不处理,同时产生code=-1016错误。此时给acceptableContentTypes加上服务器返回的类型即可。参见AFNetworking源码:

//@implementation AFJSONResponseSerializer
--------------------------------------------
- (instancetype)init {
	//...
	self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
	//...
}

同样,根据业务需要,还可配置requestSerializer属性,指派不同的请求序列化器。

小结上述补充,得到更详细的编程步骤:

  1. 创建NSURLSessionConfiguration实例,可配置额外HTTP信息
  2. 创建需要的SessionManager,如AFHTTPSessionManager,可配置其requestSerializer、responseSerializer属性。

    • 配置请求序列化器,如[AFHTTPRequestSerializer serializer]
    • 配置响应序列化器,如[AFHTTPResponseSerializer serializer]
    • 配置响应序列化器的可接受内容类型acceptableContentTypes,如acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"text/html", nil]
  3. 开始GET、POST、PUT、DELETE等请求,这些请求都返回NSURLSessionTask子类实例,拥有暂停、取消等操作。

获取普通网页文本

AFHTTPSessionManager *manager = [[AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", @"text/javascript", nil];
[manager GET:@"http://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
    NSLog(@"HTML: %@", [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
    NSLog(@"visit error: %@", error.description);
}];

获取JSON数据

请求JSON数据时,因AFHTTPSessionManager默认的responseSerializer为JSON解析类型,则无需额外配置,直接GET请求即可。

NSURL *baseURL = [NSURL URLWithString:@"http://localhost/"];
AFHTTPSessionManager *manager=[[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:config];
NSDictionary *params=[[NSDictionary alloc] initWithObjectsAndKeys:@"8",@"id",nil];
[manager GET:@"/json" parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
    NSDictionary *object=(NSDictionary *)responseObject;
    NSLog(@"response message: %@",object[@"message"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
    NSLog(@"visit error: %@", error);
}];

下载图片并写入文件

responseSerializer = [AFImageResponseSerializer serializer];
UIImage *secImg = responseObject;
NSData *pngData = UIImageJPEGRepresentation(img, 0.4);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePathName =[[paths objectAtIndex:0]stringByAppendingPathComponent:str];
[pngData writeToFile:filePathName atomically:YES];

下载文件

可通过创建NSMutableURLRequest实例并配置下载方式为GET或POST。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://www.baidu.com/img/bdlogo.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination: ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
	NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
    return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];

NSURLSessionDownloadTask可用于网络请求的取消、暂停和恢复。

默认下载操作是挂起的,必须手动恢复下载,所以发送[NSURLSessionDownloadTask resume]

下载大文件

下载大文件时,最好直接从流写到文件中,尽量不要加载到内存,否则在高速网络的情况下,我们的应用来不及处理峰值内存压力从而崩溃。这个问题在使用AFHTTPRequestOperation会出现,解决办法是配置其outputStream属性,如

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];

而AFURLSessionManager是直接写入文件,不存在上述问题。

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:candidateURL];
// 设置HTTP请求方法为HEAD,下载网络请求的头文件,这样可在下载前得到文件的大小信息。
[request setHTTPMethod:@"HEAD"];
// 读取下载任务的countOfBytesExpectedToReceive属性得到预估需下载的字节数
[NSURLSessionDownloadTask countOfBytesExpectedToReceive]

参考:
calculating total progress of downloading multiple file with AFNetworking

批量下载

当提交多个下载任务时,即批量下载,在AFURLSessionManager没提供批量任务完成的通知机制,但AFURLConnectionOperation提供了batchOfRequestOperations:progressBlock:completionBlock:方法。另外,也可将任务放到同一个GCD Dispath Group中,但是,放到GCD中的任务不可取消。

分段断点下载(多线程断点下载)

像迅雷一样,同一个文件分成多个多线程下载,最后合成一个文件。所有的关键设置全在NSMutableRequest中,参考代码:

NSMutableURLRequest *request=[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"yourURL"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod:@"GET"];
[request addValue:[NSString stringWithFormat:@"bytes=%llu-", yourDownloadedBytes] forHTTPHeaderField:@"Range"];

每段下载后需要合并文件,由NSFileHandle处理,它可随机写入,参考代码:

NSFileHandle *file=[NSFileHandle fileHandleForWritingAtPath:yourFilePath];
if (file) {
	[file seekToFileOffset:yourOffset];
	[file writeData:yourDownloadedData];
	[file closeFile];
}

参考:
IMIDownloader
AFNetworking实现程序重新启动时的断点续传

上传文件

NSMutableURLRequest *request =
    [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://localhost/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileURL:uploadFilePath
                                   name:@"file"
                               fileName:@"filename.jpg"
                               mimeType:@"image/jpeg"
                                  error:nil];
    } error:nil];
    AFURLSessionManager *manager =
    [[AFURLSessionManager alloc]
     initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSProgress *progress = nil;
    NSURLSessionUploadTask *uploadTask =
    [manager uploadTaskWithStreamedRequest:request
                                  progress:&progress
                         completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
                             if (error) {
                                 NSLog(@"Error: %@", error);
                             } else {
                                 NSLog(@"%@ %@", response, responseObject);
                             }
                         }];
    [uploadTask resume];

NSProgress获取上传进度

UIKit拓展

之前 AFNetworking 中的所有 UIKit category 都被保留并增强,还增加了一些新的 category。

加载图片

#import <UIImageView+AFNetworking.h>
NSURL *URL = [NSURL URLWithString:@"http://www.baidu.com/img/bdlogo.png"];
[uiImageViewInstance setImageWithURL:URL];

其余加载图片方法

setImageWithURL:setImageWithURL:placeholderImage:setImageWithURLRequest:placeholderImage:success:failure:
– cancelImageRequestOperation

AFNetworkActivityIndicatorManager

在请求操作开始、停止加载时,自动开始、停止状态栏上的网络活动指示图标。

UIImageView+AFNetworking

显示图片前剪裁或者加滤镜的功能。增加了 imageResponseSerializer 属性,可以轻松地让远程加载到 image view 上的图像自动调整大小或应用滤镜。比如,AFCoreImageSerializer 可以在 response 的图像显示之前应用 Core Image filter。

UIButton+AFNetworking

类似 UIImageView+AFNetworking,从远程资源加载 image 和 backgroundImage。

UIActivityIndicatorView+AFNetworking

根据指定的请求操作和会话任务的状态自动开始、停止 UIActivityIndicatorView。

UIProgressView+AFNetworking

自动跟踪某个请求或会话任务的上传/下载进度。

UIWebView+AFNetworking

支持网络请求的进度和内容转换。

网络监控

AFNetworkReachabilityManager可以单独使用,很方便,用于监控网络变化。
比如,可以在App启动后执行下面操作,启动监控器:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];

在ViewController中:

-(void)viewDidLoad
{
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
    }];
}

监听网络变化,做出相应的操作,比如弹出提示框。

几种网络状态

  • AFNetworkReachabilityStatusUnknown = -1, // 未知
  • AFNetworkReachabilityStatusNotReachable = 0, // 无连接
  • AFNetworkReachabilityStatusReachableViaWWAN = 1, // 3G 花钱
  • AFNetworkReachabilityStatusReachableViaWiFi = 2, // WiFi

常见问题

code=-1005

端口或网络断开连接,可尝试查看端口是否可用及重启模拟器。

code=-1016

acceptableContentTypes缺少支持的项,在AFURLResponseSerialization.h中搜索self.acceptableContentTypes
或直接配置实例,加上缺少的项,如@"text/html",@"text/plain"

code=3840

Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (JSON text did not start with array or object and option to allow fragments not set.) UserInfo=0x9152780 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}

添加语句manger.responseSerializer = [AFHTTPResponseSerializerserializer];即可。

编码问题

假设服务器返回a,客户端收到<61>。当用浏览器去请求时发现响应头Content-Type: text/html;charset=UTF-8,但是,用AFNetwork请求时为Content-Type:text/plain;charset=ISO-8859-1
无需修改AFURLResponseSerialization.h,只需修改manager的序列化配置即可,如

manager.requestSerializer = [AFHTTPRequestSerializerserializer];
manager.responseSerializer = [AFHTTPResponseSerializerserializer];

然后,把收到的responseObject转换一下编码

NSString *correctResponseObject =  [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];

2.0的一些变化

AFNetworking 2.0 的目标是调整原始设计中的一些奇怪的地方,同时添加强大的新架构,帮助新一代的应用程序变得更为强大。

Rocket技术

AFNetwork 2.0 遵循 Rocket 技术,Rocket 是在现有的 REST 服务器之上,通过一些 Web 标准(如Server-Sent EventsJSON Patch),实现实时的数据更新。

模块化

AFNetworking 1.0 被批评的一个地方是,它有点臃肿。其实 1.0 在类的层次上很具有模块化,但文件封装的不够方便,没办法单独分离出某个功能模块。随着时间的推移,特别是像 AFHTTPClient 这样的类,会变得很臃肿(创建请求,序列化请求参数,响应和解析,创建和管理各种操作,监控网络的可用性等都堆在一起)。

兼容 NSURLSession

在 iOS7 中 NSURLConnection 被 NSURLSession 取代,但 NSURLConnection 并没有被 deprecated,在一段时间内依然可用。不过,NSURLSession 才是未来,它解决了 NSURLConnection 的很多缺点。有人可能会说有 NSURLSession 还需要 AFNetworking 么,二者确实有重叠的地方,但 AFNetworking 作为一个更上层的抽象类,能提供的更多。2.0 兼容并扩展了 NSURLSession,铺平其中艰难的路线,最大限度的提高了易用性。

1.0迁移2.0

[《AFNetworking 2.0 迁移指南》](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide)

提高

NSURLConnection

  • AFURLConnectionOperation - 它继承于 NSOperation,负责管理 NSURLConnection,实现它的 delegate 方法。
  • AFHTTPRequestOperation - 它继承于 AFURLConnectionOperation,专门用于创建 HTTP 请求。2.0 的主要区别就是可以直接使用它,而不用再继承它,原因将会在下面的 Serialization 处解释。
  • AFHTTPRequestOperationManager - 封装 HTTP 请求的常见方式,GET / POST / PUT / DELETE / PATCH

NSURLSession

  • AFURLSessionManager - 创建和管理 NSURLSession 对象,以及此对象的数据和下载/上传等任务,实现对象和任务的代理方法。NSURLSession 的 API 本身有一些局限,AFURLSessionManager 能使其变得更好用。

AFHTTPSessionManager - 它继承于 AFURLSessionManager,封装了 HTTP 请求的常见方式,GET / POST / PUT / DELETE / PATCH

总结

为了支持最新的 NSURLSession 接口,同时兼容旧的 NSURLConnection,2.0 的核心组件将“网络请求”和“任务处理”分离。 AFHTTPRequestOperationManager 和 AFHTTPSessionManager 提供相似的功能,切换很方便,所以从 iOS 6 移植到 iOS 7 会很容易。之前绑在 AFHTTPClient 里的 serialization、security 和 reachability 模型都被分离了出来,基于 NSURLSession 和 NSURLConnection 的 API 都可复用这些模型。

序列化

2.0 架构的一个突破就是,请求和解析的可序列化。序列化的灵活性允许在网络层添加更多的商业逻辑,并更容易定制之前内置的默认行为。

  • - 符合这个协议的对象用于处理请求,它将请求参数转换为 query string 或是 entity body 的形式,并设置必要的 header。那些不喜欢 AFHTTPClient 使用 query string 编码参数的家伙,
  • - 符合这个协议的对象用于验证、序列化响应及相关数据,转换为有用的形式,比如 JSON 对象、图像、甚至基于 Mantle 的模型对象。相比没完没了地继承 AFHTTPClient,现在 AFHTTPRequestOperation 有一个 responseSerializer 属性,用于设置合适的 handler。同样的,再也没有没用的受 NSURLProtocol 启发的 request operation 类注册,取而代之的还是很棒的 responseSerializer 属性。

安全

AFNetworking 支持 SSL pinning。这对涉及敏感数据的 App 很重要。

AFSecurityPolicy - 这个类通过特定证书和公共密钥评估链接的安全性和可信任度。在你的 App bundle 中添加服务器证书有助于防止“中间人攻击”。

可达性

另一个从 AFHTTPClient 中分离的功能是网络的可达性。现在你可以单独使用它,或者作为 AFHTTPRequestOperationManager / AFHTTPSessionManager 的一个属性来使用。

AFNetworkReachabilityManager - 负责监控当前的网络可达性,当网络的可达性发生改变时,提供相应的 callback 和通知。

实时

AFEventSource - 用 Objective-C 实现的 EventSource DOM API。客户端和服务器建立持久 HTTP 连接,服务器会把新的 Event 实时推给客户端。客户端收到的信息格式是JSON Patch,然后 JSON Patch 被转化为 AFJSONPatchOperation 对象的数组。可以将这些 patch operation 应用到之前从服务器获取的持久性数据集。示例代码参考:

NSURL *URL = [NSURL URLWithString:@"http://example.com"];
AFHTTPSessionManager *manager = [[AFHTTPClient alloc] initWithBaseURL:URL];
[manager GET:@"/resources" parameters:nil success:^(NSHTTPURLResponse *response, id responseObject) {
    [resources addObjectsFromArray:responseObject[@"resources"]];
    [manager SUBSCRIBE:@"/resources" usingBlock:^(NSArray *operations, NSError *error) {
        for (AFJSONPatchOperation *operation in operations) {
            switch (operation.type) {
                case AFJSONAddOperationType:
                    [resources addObject:operation.value];
                    break;
                default:
                    break;
            }
        }
    } error:nil];
} failure:nil];

缓存策略

NSURLRequest默认的缓存策略是NSURLRequestUseProtocolCachePolicy,网络请求是否用缓存是由 HTTP Cache-Control 决定,而在实际开发中由于种种原因(服务端为了简化系统等),接口的缓存时间都设置的非常长或者不准,这种情况下就会出现服务端数据更新但是 AFN 拿到的还是旧数据,因为他直接读的缓存。

得益于 AFN 优秀的架构设计,这个问题也很好解决,继承 AFHTTPClient 然后重写

requestWithMethod:path:parameters::

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters
{
    NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];

    return request;
}

Response 类型判断

以 AFJSONRequestOperation 为例,只有 Content-Type 是 @"application/json", @"text/json", @"text/javascript" 或 URL pathExtension 是 json 的才会被认为是 JSON 类型,其他都不认。很多服务端接口都没有 Content-Type 返回或者直接丢一个 text/html,请求也不是 json 结尾,但返回内容确实又是 JSON 数据,这时候 AFN 就很无力。

URL加载系统

NSURLConnection 是 Foundation URL 加载系统的基石。一个 NSURLConnection 异步地加载一个 NSURLRequest 对象,调用 delegate 的 NSURLResponse / NSHTTPURLResponse 方法,其 NSData 被发送到服务器或从服务器读取;delegate 还可用来处理 NSURLAuthenticationChallenge、重定向响应、或是决定 NSCachedURLResponse 如何存储在共享的 NSURLCache 上。

NSOperation是抽象类,模拟单个计算单元,有状态、优先级、依赖等功能,可以取消。

源码阅读

类图
整体类图

增强模块

AFNetworking-Extensions

同类型第三方库

  • STHTTPRequest - 基于 NSURLConnection,支持 synchronous+asynchronous blocks,支持文件上传,非常简单轻量的封装,值得一试。

引伸话题

MVCNetworking

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值