iOS 8:使用AFNetworking, SDWebimage和OHHTTPStubs

源地址:http://blog.shiqichan.com/using-afnetworking-sdwebimage-and-ohhttpstubs/

写了个示例,放在GitHub上。

这是运行的动画:

技术分享

以下内容主要介绍:

  • 基于AFNetworking的HTTP操作,GET获取网页和JSON数据,上传文件,下载文件,以及加载图片
  • 基于SDWebimage的加载图片
  • 基于OHHTTPStubs的伪造网络响应用于测试(stub),而且可以模拟出网络的延时

使用基于NSURLSession的AFNetworking API

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

  • 基于NSURLConnection
  • 基于NSURLSession

后者是新的API,用于iOS 7 / Mac OS X 10.9及以上版本。

这篇文章写的很好:从 NSURLConnection 到 NSURLSession,说明后者做了哪些改善和加强。

现在越来越多的iOS项目最低要求iOS 7,让我们可以开始尝试使用这种新的方式。

GET请求,获取普通网页文本

AFHTTPSessionManager是使用NSURLSession的API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSURL *baseURL = [NSURL URLWithString:@"http://localhost/"];
 
//设置和加入头信息
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[config setHTTPAdditionalHeaders:@{ @"User-Agent" : @"My Browser"}];
 
AFHTTPSessionManager *manager=[[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:config];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
 
//设置GET请求的参数
NSDictionary *params=[[NSDictionary alloc] initWithObjectsAndKeys:@"3",@"id",nil];
 
//发起GET请求
[manager GET:@"" parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"HTML: %@", [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"visit error: %@",error);
}];

为了方便测试,这个示例里写了个简单的Web Server,httpServer.js

运行httpServer.js,需要安装node.js环境。然后:

sudo node httpServer

我使用了80端口,在Mac环境下是需要root权限的。

GET请求,获取JSON数据

方法和GET请求网页文本大同小异,个别参数或者设置对象上有不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSURL *baseURL = [NSURL URLWithString:@"http://localhost/"];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[config setHTTPAdditionalHeaders:@{ @"User-Agent" : @"My Browser"}];
 
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);
}];

下载文件

AFNetworking API会返回NSURLSessionDownloadTask,可用于网络请求的取消、暂停和恢复。

其实上文中的GET方法也返回了这个对象,只不过下载文件时间可能会较长,有可能有这方面的需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
uploadFilePath=filePath;
}];
[downloadTask resume];

使用AFNetworking加载图片

需要引入一下:

1
#import <UIImageView+AFNetworking.h>

然后,UIImageView就会有setImageWithURL供使用。

1
2
NSURL *URL = [NSURL URLWithString:@"http://www.baidu.com/img/bdlogo.png"];
[imageView setImageWithURL:URL];

另外,提供了其他方法,可设置占位图片,图片下载成功和失败的处理,以及停止图片下载的方法。

1
2
3
4
setImageWithURL:
setImageWithURL:placeholderImage:
setImageWithURLRequest:placeholderImage:success:failure:
cancelImageRequestOperation

使用SDWebimage加载图片

SDWebImage,调用方式和AFNetworking类似,功能更强大,使用也很普及。

需要引入:

1
#import <SDWebImage/UIImageView+WebCache.h>

代码:

1
2
NSURL *URL = [NSURL URLWithString:@"http://www.sogou.com/images/logo/new/sogou.png"];
[imageView sd_setImageWithURL:URL];

下面是完整的方法列表:

1
2
3
4
5
6
7
8
9
10
11
12
sd_imageURL
sd_setImageWithURL:
sd_setImageWithURL:placeholderImage:
sd_setImageWithURL:placeholderImage:options:
sd_setImageWithURL:completed:
sd_setImageWithURL:placeholderImage:completed:
sd_setImageWithURL:placeholderImage:options:completed:
sd_setImageWithURL:placeholderImage:options:progress:completed:
sd_setImageWithPreviousCachedImageWithURL:andPlaceholderImage:options:progress:completed:
sd_setAnimationImagesWithURLs:
sd_cancelCurrentImageLoad
sd_cancelCurrentAnimationImagesLoad

比AFNetworking选项更多一些,比如可以设置SDWebImageOptions

1
2
3
4
5
6
7
8
9
10
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions ) {
SDWebImageRetryFailed = 1 < < 0,
SDWebImageLowPriority = 1 < < 1,
SDWebImageCacheMemoryOnly = 1 < < 2,
SDWebImageProgressiveDownload = 1 < < 3,
SDWebImageRefreshCached = 1 < < 4,
SDWebImageContinueInBackground = 1 < < 5,
SDWebImageHandleCookies = 1 < < 6,
SDWebImageAllowInvalidSSLCertificates = 1 < < 7,
};

还有:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize)

  • 可以用一组图片生成动画:sd_setAnimationImagesWithURLs
  • 使用之前的Cache先显示图片?sd_setImageWithPreviousCachedImageWithURL,这个我从字面意思理解,还没有使用
  • 有个process block,sd_setImageWithURL:placeholderImage:options:progress:completed:,可以获得receivedSizeexpectedSize字节参数,用来显示进程百分比

另外,iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache),这篇文章测试和对比,结论也是SDWebimage更好一些。

AFNetworking上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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获取上传进度(具体怎么做呢?)

使用OHHTTPStubs伪造HTTP响应

OHHTTPStubs,可用来伪造HTTP响应,这样不依赖服务器端,iOS的开发人员就可以测试网络服务了。

这个API,同时支持:

  • NSURLConnection
  • NSURLSession

不过,有个问题需要注意,如果App要上AppStore,是不能连接OHHTTPStubs的。

下面说下怎么使用,先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSString *baseUrl=@"http://localhost";
 
//针对http://locahost/json请求的mock
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSDictionary *params=[NSDictionary objectsFromURLQueryString:request.URL.query];
NSLog(@"id: %@",params[@"id"]);
 
return [[request.URL absoluteString] rangeOfString:[NSString stringWithFormat:@"%@/json",baseUrl]].location==0;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
NSLog(@"reqeust: %@",request);
NSString* fixture = OHPathForFileInBundle(@"test.json",nil);
return [[OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200 headers:@{@"Content-Type":@"text/json"}
]requestTime:0 responseTime:0];
}];

基本思路是,调用方法,通过2个回调(Block)实现对指定HTTP请求的响应伪造:

  • 是否是要拦截的请求
  • 拦截后,创建一个响应

在上述代码里还演示了:

  • 如何从URL中提取GET请求的参数,这里用到了:URLQueryToCocoa
  • 使用本地文件作为JSON数据,加入到HTTP响应中
  • 可以设置请求和响应的延时,requestTime:0 responseTime:0,这个相当有用

 

还可以用于伪造图片的响应,测试了一下,上述的AFNetworking以及SDWebimage都有效。

1
2
3
4
5
6
7
8
9
10
//GET image with sdwebimage
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [[request.URL absoluteString] isEqualToString:@"http://www.sogou.com/images/logo/new/sogou.png"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
NSLog(@"reqeust: %@",request);
NSString* fixture = OHPathForFileInBundle(@"taobao.png",nil);
return [[OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200 headers:@{@"Content-Type":@"image/png"}
]requestTime:0 responseTime:0];
}];

测试的时候,要注意,之前可能是通过真实网络获取的,因此会有缓存。需要把App删除,重新安装测试。

应该能看到类似下面的效果(使用的时本地图片了):

技术分享

 

OHHTTPStubs这些代码,只需在App启动加载一次即可,可写在AppDelegate中:

1
2
3
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[HttpMock initMock];

代码屏蔽掉,自然就使用真实网络了。

编译正式使用代码的时候,可以考虑条件编译。

AFNetworking的网络监控API

提供的AFNetworkReachabilityManager可以单独使用,很方便,用于监控网络变化。

比如,可以在App启动后执行下面操作,启动监控器:

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

 

在ViewController中:

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

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

 

正式使用时:

  • 可考虑在AppDelegate中setReachabilityStatusChangeBlock,当状态变化后,通过NSNotification发出通知
  • 在各个ViewController的viewDidAppearviewWillDisappear中监听和取消监听通知

这是设想,还没有付诸实施,也许还有问题。

不过至少不应该像本例中的,在ViewController中使用setReachabilityStatusChangeBlock

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。