在 NSURLConnection.m 我们可以看到以下声明:
DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection.
话虽如此,但是可以通过它了解一些知识,还有起码能看懂就项目的网络请求。

要学习使用 NSURLConnection 发起 HTTP 请求并接收服务器的响应,得先了解以下这些类:

  • NSURL:请求地址
  • NSURLRequest:一个 NSURLRequest 对象就代表一个请求,它包含的信息有
    • 一个 NSURL 对象
    • 请求方法、请求头、请求体
    • 请求超时
    • … …
  • NSMutableURLRequest:NSURLRequest 的子类
  • NSURLConnection
    • 负责发送请求,建立客户端和服务器的连接
    • 发送数据给服务器,并收集来自服务器的响应数据

NSURLConnection 发送请求

使用 NSURLConnection 发送请求的步骤很简单

  • 创建一个 NSURL 对象,设置请求路径
  • 传入 NSURL 创建一个 NSURLRequest 对象,设置请求头和请求体
  • 使用 NSURLConnection 发送请求

NSURLConnection 的使用步骤

1. 发送同步请求

注意同步请求是阻塞式的,数据不返回回来之前是不会往下执行的,因此一般不建议用这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=angelen&pwd=123456"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
// 发送同步请求(阻塞式)此方法于 iOS 9.0 过时
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// 这里暂时解析成 NSString(本来是 JSON 的)
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@\n%@\n%@", string, response.allHeaderFields, error); // response.allHeaderFields 是请求头
}
// 打印:
{"error":"用户名不存在"}
{
"Content-Type" = "application/json;charset=UTF-8";
Date = "Wed, 09 Nov 2016 02:33:23 GMT";
Server = "Apache-Coyote/1.1";
"Transfer-Encoding" = Identity;
}
(null)

⭐️请求方法

1
+ (nullable NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse * _Nullable * _Nullable)response error:(NSError **)error;

中,可以看到参数 response 和 error 的类型都是有两个 * 的,因此要把地址传递进去。

2. 发送异步请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=angelen&pwd=123456"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 发送异步请求(非阻塞式)此方法于 iOS 9.0 过时
// 注意:这个 queue 如果不是主队列,如果想要在 block 刷新 UI 或者关掉 HUD 等就要回到主线程才能做。
// 这里也可以写主队列,这样子在 block 就可以直接刷新 UI
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 这里暂时解析成 NSString(本来是 JSON 的)
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@\n%@\n%@", string, ((NSHTTPURLResponse *)response).allHeaderFields, connectionError); // 事实上,在这里的 response 是 NSHTTPURLResponse
}];
NSLog(@"-->");
}
// 打印:
-->
{"error":"用户名不存在"}
{
"Content-Type" = "application/json;charset=UTF-8";
Date = "Wed, 09 Nov 2016 02:44:29 GMT";
Server = "Apache-Coyote/1.1";
"Transfer-Encoding" = Identity;
}
(null)

发送异步请求除了用 block 回调,还可以使用代理(NSURLConnectionDataDelegate):

1
2
3
4
5
6
7
8
/* Designated initializer */
// 在 startImmediately = NO 的情况下,需要调用 start 方法开始发送请求
- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately;
// 就是调用上面的方法,startImmediately = YES,会马上发起请求
- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;
+ (nullable NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;

大致使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@interface ViewController ()<NSURLConnectionDataDelegate>
/** 接收到的数据 */
@property (strong, nonatomic) NSMutableData *responseData;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"https://pic2.zhimg.com/v2-02517e3fbfc46bc5eb0bc2bdc5458715_b.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 会立刻发起请求(这里可以换成其他两种写法,都差不多)
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
// 接收到响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"didReceiveResponse");
// 初始化 responseData
self.responseData = [NSMutableData data];
}
// 接收到数据(如果数据比较大,会接收多次)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"didReceiveData -> %zd", data.length);
[self.responseData appendData:data];
}
// 接收数据完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"connectionDidFinishLoading");
// 当数据接收完毕时,可以在这里做后续的操作(这里在主线程执行的)
NSLog(@"responseData -> %zd", self.responseData);
}
// 请求失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// 这个是 NSURLConnectionDataDelegate 父类 NSURLConnectionDelegate 的方法
NSLog(@"didFailWithError -> %@", error);
}
@end
// 打印
didReceiveResponse
didReceiveData -> 15785
didReceiveData -> 16384
didReceiveData -> 16384
didReceiveData -> 16384
didReceiveData -> 16384
didReceiveData -> 16384
didReceiveData -> 16384
didReceiveData -> 14947
connectionDidFinishLoading
responseData -> 106102872444432

上面两个例子都是是发送同步 GET 请求和异步 GET 请求,其实 POST 请求也差不多,只不过参数是放到请求体里而不是 URL 里,而且不能使用 NSURLRequest 只能使用 NSMutableURLRequest。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
// 注意:NSURLRequest 不能够修改请求方法,默认是 GET,只能用 NSMutableURLRequest
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法
request.HTTPMethod = @"POST"; // 注意要大写
// 设置请求体
request.HTTPBody = [@"username=angelen&pwd=123456" dataUsingEncoding:NSUTF8StringEncoding];
// 设置超时时间为 5s
request.timeoutInterval = 5;
// 还可以设置请求头(不过想要告知服务器是什么设备,也可以通过参数传递)
[request setValue:@"iOS 10.1" forHTTPHeaderField:@"User-Agent"];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
if (connectionError) {
NSLog(@"--> %@", connectionError);
} else {
NSLog(@"--> %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
}];
}
// 打印
--> {"error":"用户名不存在"}

注意,如果 URL 中含有中文,需要做处理,如:

1
2
3
4
5
NSString *urlString = @"http://120.25.226.186:32812/login?username=小良GG&pwd=123456";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"--> %@", url);
// 打印
--> (null)

解决方案:

1
2
3
4
5
6
7
8
9
10
NSString *urlString = @"http://120.25.226.186:32812/login?username=小良GG&pwd=123456";
// iOS 9.0 已过期,采用 stringByAddingPercentEncodingWithAllowedCharacters:
// urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"--> %@", url);
// 打印
http://120.25.226.186:32812/login?username=%E5%B0%8F%E8%89%AFGG&pwd=123456