向大家介绍Alamofire的基本用法,即如何通过iOS客户端发起各种HTTP请求,并以Alamofire提供的不同方式处理HTTP response。为了简单起见,在我们的例子里,Alamofire的应用都不会和UI代码相关,我们只演示HTTP通信的部分。首先,我们从一个最简单的HTTP请求开始。
发起一个最基本的HTTP请求
打开AlamoFireDemo的ViewController.swift文件,添加下面的代码:
import UIKit
import Alamofire
"引入"了Alamofire module之后,我们就可以使用它提供的各种API了。为了测试各种HTTP action,我们需要一个与之配合的服务端程序。幸运的是,至少现在我们还不用专门去写它,httpbin.org为我们提供了方便的HTTP测试API:

包括GET一个页面、一个JSON、各种REST方法、常用的HTTP status、认证、下载等。它们可以很方便的让我们来测试Alamofire提供的各种功能。我们从GET /ip开始,httpbin会返回一个类似下面这样的JSON:
{
"origin": "106.185.45.242"
}
回到Xcode,在viewDidLoad()里,添加下面的代码:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "https://httpbin.org/ip")
}
这样,我们就完成了一次对httpbin.org的GET请求。看似简单,却有三点需要说明:
第一点是Alamofire.request这样的用法,该如何理解呢?Alamofire是类名吗?request是一个class method吗?实际上不是这样的。前面的Alamofire是因为我们一开始引入了Alamofire module之后,开启的名字空间;而request则是这个名字空间里的一个函数,我们可以在Alamofire的源代码里找到它。
第二点是request的第一个参数,表示我们采用的HTTP方法。它是Alamofire定义的一个enum,在这里:
public enum Method: String {
case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT
}
第三点是Alamofire发起的HTTP request是和App主线程并行执行的,它并不会阻塞App的UI。
理解了这三点,上面的代码就没有任何问题了。接下来,我们来看如何处理HTTP response。
处理HTTP response
Alamofire有一个最原始的获取HTTP response的方法,像这样:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "https://httpbin.org/ip")
.response { request, response, data, error in
print(request)
print(response)
print(data)
print(error)
}
}

从图中的结果就可以看到closure的四个参数对应的类型了,它们分别代表request/response的基本信息,返回内容的原始数据以及错误信息。事实上,如果我们查看response的实现,就会发现更多内容:
public func response(
queue queue: dispatch_queue_t? = nil,
completionHandler:
(NSURLRequest?,
NSHTTPURLResponse?,
NSData?,
NSError?) -> Void)
-> Self
{
delegate.queue.addOperationWithBlock {
dispatch_async(queue ?? dispatch_get_main_queue()) {
completionHandler(self.request,
self.response,
self.delegate.data,
self.delegate.error)
}
}
return self
}
response实际上有2个参数,只不过第一个参数有默认值,所以,我们使用了一个trailing closure定义了它的第二个参数。当第一个参数queue为nil时,Alamofire默认会在App的主线程中执行我们的handler,方便我们在收到HTTP请求的时候更新UI。
当我们通过这种方式得到了一个表示HTTP response的NSData对象后,我们就可以把它变成Image、序列化成JSON等等。但是如果真是这样,我们就完全没必要使用Alamofire了,因为这几乎和NSURLSession的"套路"是一样的。实际上,我们很少直接使用这个原始的response,Alamofire为我们提供了更方便的序列化结果的方法:
responseData(/*completionHandler*/)
responseString(encoding: NSStringEncoding,
/*completionHandler*/)
responseJSON(options: NSJSONReadingOptions,
/*completionHandler*/)
responsePropertyList(options: NSPropertyListReadOptions,
/*completionHandler*/)

它们之中,后三个方法的第一个参数可以先暂时忽略,因为它们都是有默认值的。这四个方法的共同之处就是它们有一个共同的handler,这个handler接受一个类型为Response<nsdata, nserror="" style="box-sizing: border-box;">的参数,用来让我们在Alamofire完成序列化之后,做进一步的处理。</nsdata,>
而我们查看Response的源代码就会发现,它封装了之前我们用过的request / response / data:
public struct Response {
/// The URL request sent to the server.
public let request: NSURLRequest?
/// The server's response to the URL request.
public let response: NSHTTPURLResponse?
/// The data returned by the server.
public let data: NSData?
/// The result of response serialization.
public let result: Result
// Omit for simplicity...
}
并且,它还额外提供了一个Result<value, error="" style="box-sizing: border-box;">类型的属性result,方便我们读取序列化过之后的结果(简单起见,例子中省略了实现的部分):</value,>
public enum Result {
case Success(Value)
case Failure(Error)
public var isSuccess: Bool {
// Omit for simplicity...
}
public var isFailure: Bool {
// Omit for simplicity...
}
public var value: Value? {
// Omit for simplicity...
}
public var error: Error? {
// Omit for simplicity...
}
}
另外,Result还实现了CustomStringConvertible protocol,我们可以直接print一个Result对象。
在理解了这些处理HTTP response的类型之后,我们就可以很方便的处理Alamofire返回的序列化结果了。
处理序列化之后的String和JSON
首先来看String:
Alamofire.request(.GET, "https://httpbin.org/ip")
.response { request, response, data, error in
print(request)
print(response)
print(data)
print(data.dynamicType)
print(error)
print(error.dynamicType)
}
.responseString { response in
print("String:====================")
switch response.result {
case .Success(let str):
print("\(str.dynamicType)")
case .Failure(let error):
print("\(error)")
}
}
在这里,Alamofire的各种response方法是可以串联在一起的,由于response.result是一个enum,我们可以通过一个switch...case来处理它的两种结果,并且在.Success里,直接读取它的associated value作为返回的字符串;在.Failure里读取包含错误信息的对象。

接下来,我们来看JSON。为了得到一个相对复杂一些的JSON,这次,我们请求httpbin的/get,然后,会得到一个类似下面的返回结果:
{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4",
"Cookie": "_ga=GA1.2.1123360294.1453608486; _gat=1",
"Host": "httpbin.org",
"Referer": "https://httpbin.org/",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2646.0 Safari/537.36"
},
"origin": "106.185.45.242",
"url": "https://httpbin.org/get"
}
首先我们把之前例子里的请求的URL修改成https://httpbin.org/get:
Alamofire.request(.GET, "https://httpbin.org/get")
然后,把responseJSON串联在responseString后面:
Alamofire.request(.GET, "https://httpbin.org/get")
.responseString(completionHandler: { response in
print("String ===============")
switch response.result {
case .Success(let str):
print("\(str.dynamicType)")
print("\(str)")
case .Failure(let error):
print("\(error)")
}
})
.responseJSON(completionHandler: { response in
print("JSON ================")
})
为了更好的理解解析JSON的方式,我们来看个图:

对于一个JSON的返回结果来说,它的所有KEY的类型,都是String,而VALUE的类型,有可能是一个String值,有可能是一个数组,还有可能是一个对象。因此,Alamofire只能用一个AnyObject类型的对象表示它。通常,我们要把它转化成一个<key style="box-sizing: border-box;">NSDictionary</key>或Dictionary<String, AnyObject>类型的对象。然后,根据访问的KEY的类型,做进一步的类型转换:
- 读取key1时,把结果转化成String
- 读取key2时,把结果转化成Array
- 读取key3时,把结果转化成其它的Dictionary
理解了处理JSON的方法,我们就可以在responseJSON中处理httpbin的返回结果了:
Alamofire.request(.GET, "https://httpbin.org/get")
.responseJSON(completionHandler: { response in
print("JSON ================")
switch response.result {
case .Success(let json):
let dict = json as! Dictionary
let origin = dict["origin"] as! String
let headers = dict["headers"] as! Dictionary
print("origin: \(origin)")
let ua = headers["User-Agent"]
print("UA: \(ua)")
case .Failure(let error):
print("\(error)")
}
})
在上面的代码中我们可以看到,在读取"origin"和"headers"时,我们并不用类型转换,并且,在下面的结果里,变量origin和ua各自代表了JSON中的内容:

网友评论