使用
AFNetworking有2套用于网络操作的API:
- 基于NSURLConnection
- 基于NSURLSession,要求iOS 7以上版本
通过配置CocoaPods subspecs可挑选需要的模块而无需使用整个AFNetworking。
本文讲解基于NSURLSession的新接口。一个返回数据为JSON格式的HTTP GET请求,最简单的编程步骤为:
- 创建AFHTTPSessionManager,如
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager] 。 -
调用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源码如下
------------------------------
- (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源码:
--------------------------------------------
- (instancetype)init {
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
}
同样,根据业务需要,还可配置requestSerializer属性,指派不同的请求序列化器。
小结上述补充,得到更详细的编程步骤:
- 创建NSURLSessionConfiguration实例,可配置额外HTTP信息
-
创建需要的SessionManager,如AFHTTPSessionManager,可配置其requestSerializer、responseSerializer属性。
- 配置请求序列化器,如
[AFHTTPRequestSerializer serializer] - 配置响应序列化器,如
[AFHTTPResponseSerializer serializer] - 配置响应序列化器的可接受内容类型acceptableContentTypes,如
acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"text/html", nil] -
开始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];
[request setHTTPMethod:@"HEAD"];
[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。
类似 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 Events,JSON 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
参考
|