Alamofire 教程:入门指南

注意:更新iOS 9.3, and Swift 2.2- 2016年4月2日

原文 https://www.raywenderlich.com/121540/alamofire-tutorial-getting-started

Alamofire 是一个为 iOS 和 Mac OS,基于 Swift 的 HTTP 网络库。它在 Apple 的基础网络库上提供了一个优雅的接口,简化了许多常见的网络任务。

Alamofire 提供链式地请求与响应方法,JSON 参数以及响应的序列化和授权,等等。在本教程中,你将使用 Alamofire 来完成基本的网络任务,包括上传文件以及使用第三方库 RESTful API 来请求数据。

Alamofire 的优雅是一个事实,它完全由 Swift 编写,没有任何 Objective-C 的代码,也不是继承自 AFNetworking。

你需要理解一些 HTTP 的基本概念,以及了解苹果的网络类,比如NSURLSession和NSURLConnection。

虽然 Alamofire 掩盖了一些实现细节,但是如果你需要解决你的网络请求,有一些背景知识也是不错的。你也需要使用 CocoaPods 把 Alamofire 安装到你的教程工程中。

入门指南

下载示例初始工程.

这个Alamofire应用程序名为PhotoTagger;下载完成后,打开工程运行,会发现中间有一个按钮可以选择图片,点击按钮会访问系统的相册,随便选择一张图片,背景就会被这张图片代替。但是现在的工程是一个初始工程,好多功能没有完成,如下图:

在Xcode中构建和运行这个项目,您将看到以下信息:


点击选择照片,然后选择一个照片。背景图像将被替换为你选择的图片。

打开的Main.storyboard,你就会看到另外的用于显示标签和颜色的页面已经添加好了。剩下的工作就是上传图像和获取标签和颜色。

Imagga API

Imagga是一个图像识别平台,它提供了图像标签api为开发人员和企业构建可延展,密集型云应用程序。你可以看一下demo.

您将需要创建一个免费的Imagga开发者账户。Imagga需要一个授权添加在每个HTTP的授权头,所以只有拥有帐户的人才可以使用他们的服务。访问https://imagga.com/auth/signup/hacker并填写表单。在您创建完您的帐户后,请查看dashboard:

Authorization选项是一个 token,后面你会用到。

注意:确保你拷贝了整个 token,因为这个 token 很长。

你将会使用 Imagga 作为上传图片的服务器,给图片标注和设置颜色。你可以在http://docs.imagga.com了解到所有的 API。

安装库文件

在项目的主目录下创建一个名为Podfile的文件的并添加以下内容:

platform:ios,'9.0'

inhibit_all_warnings!

use_frameworks!

target'PhotoTagger'do

pod'Alamofire','~> 3.1.2'

end

接下来,用pod install命令安装CocoaPods中的第三方框架。如果你没有CocoaPods安装在你的机器上,看看如何使用CocoaPods迅速教程中获得更多信息How to Use CocoaPods with Swift

关闭项目并打开新创建的PhotoTagger.xcworkspace。编译并运行您的项目,你不会发现应用程序运行的任何视觉的变化。这很好——你的下一个任务是添加一些HTTP调用RESTful服务来检索JSON。

REST, HTTP, JSON — 都是什么?

如果你来到这个Alamofire教程并且对于如何使用第三方服务用于互联网没有太多经验,你可能想知道这些缩写是什么意思!

HTTP:是应用协议,或一组规则,网站使用从web服务器传输数据到你的屏幕上。你应该见过HTTP(或HTTPS)中排列在每个你输入到web浏览器的URL面前。你可能听说过的其他应用程序协议,如FTP、Telnet和SSH。HTTP方法定义了几个请求,或动词,客户端(web浏览器或应用程序)使用来表明所需的行动:

GET: 用于获取数据,比如一个网页,但不改变任何服务器上的数据。

HEAD: 和GET相同,但只有返回头部信息并没有实际的数据。

POST: 用于发送数据到服务器,常用当填充表单并单击submit。

PUT: 用于发送数据到特定位置。

DELETE: 从提供的特定位置删除数据。

REST,或具象状态传输,是一组规则设计一致、易于使用和维护的web api。REST有几个架构规则,执行诸如不跨请求持久化状态,发出请求缓存,并提供统一的接口。这使得像你一样的应用程序开发人员很容易将API集成到应用程序中,而不需要跟踪数据在请求的状态。

JSON支持JavaScript对象表示法,它提供了一个简单的、人类可读的和轻便的机制在两个系统之间传输数据。JSON有数量有限的数据类型:字符串、布尔值、数组、对象/字典,空和数字;没有整数与小数之间的区别。苹果供应NSJSONSerialization类来帮助你将内存中对象转换为JSON,反之亦然。

HTTP、REST和JSON的结合可为一个开发人员提供的相当一部分可用的web服务。试图了解每一个小模块是如果工作的。像Alamofire这样的库可以帮助减少使用这些服务的复杂性,让你更快地启动和运行比你可能没有帮助。

Alamofire有什么好处?

你为什么需要Alamofire?苹果已经提供了NSURLSession和其他类通过HTTP下载内容,那么为什么还要另一个第三方库将问题变得复杂?

简短的回答是,Alamofire基于NSURLSession,但它使你免受写作样板代码使得编写网络代码更加容易。你可以很轻松的在互联网上访问数据,并且您的代码将更干净、更容易阅读。

Alamofire有几个主要功能:

.upload: 上传文件多部分、流、文件或数据的方法。

.download: 下载文件或继续一个已经在进程中的下载任务。

.request: 每一个HTTP请求与文件传输无关。

这些Alamofire函数是模块范畴的,而不是一个类或结构体。有底层块Alamofire类和结构体,像Manager,Request, 和Response;然而,你不需要完全理解整个结构就可以开始使用它。

这里有一个用苹果NSURLSession和Alamofire处理相同的网络操作的例子:

// With NSURLSession

public func fetchAllRooms(completion:([RemoteRoom]?)->Void){

   let url = NSURL(string:"http://localhost:5984/rooms/_all_docs?include_docs=true")!

   let urlRequest = NSMutableURLRequest(

      URL:url,   

      cachePolicy:.ReloadIgnoringLocalAndRemoteCacheData, 

      timeoutInterval:10.0*1000)

urlRequest.HTTPMethod="GET"

urlRequest.addValue("application/json", forHTTPHeaderField:"Accept")

let task = urlSession.dataTaskWithRequest(urlRequest){(data, response, error)->Void in

guard error == nil else {

print("Error while fetching remote rooms:\(error)")

completion(nil)

return

}

guard let json = try ? NSJSONSerialization.JSONObjectWithData(data!,      options:[])as?[String:AnyObject] else {

print("Nil data received from fetchAllRooms service")

completion(nil)

return

}

guard let rows = json["rows"]as?[[String:AnyObject]]{

print("Malformed data received from fetchAllRooms service")

completion(nil)

return

}

var rooms = [RemoteRoom()

for roomDict in rows{

rooms.append(RemoteRoom(jsonData:roomDict))

}

completion(rooms)

}

task.resume()

}

与此相反:

// With Alamofire

func fetchAllRooms(completion:([RemoteRoom]?)->Void){

Alamofire.request(

.GET,

"http://localhost:5984/rooms/_all_docs",   

parameters:["include_docs":"true"],   

encoding:.URL)

.validate()

.responseJSON{(response)->Void in

guard response.result.isSuccess else {

print("Error while fetching remote rooms:\(response.result.error)")

completion(nil)

return

}

guard let value = response.result.valueas?[String:AnyObject],     

  rows=value["rows"]as?[[String:AnyObject]] else {

print("Malformed data received from fetchAllRooms service")

completion(nil)

return

}

var rooms=[RemoteRoom]()

for roomDict in rows{

rooms.append(RemoteRoom(jsonData:roomDict))

}

completion(rooms)

}

}

你可以看到,该请求使用Alamofire来设置相关方法时更短且更清晰。你可调用responseJSON(options:completionHandler:)并在响应对象上调用validate()简化了错误处理。

上传文件

打开ViewController.swift。并添加下面的类扩展的文件:

// Networking calls

extension ViewController{

func uploadImage(image:UIImage, progress:(percent:Float)->Void,

    completion:(tags:[String], colors:[PhotoColor])->Void){

guard let imageData=UIImageJPEGRepresentation(image,0.5) else {

print("Could not get JPEG representation of UIImage")

return

}

}

}

上传图片到Imagga的第一步是获取正确格式的API。上图,图片选择器API返回一个UIImage实例并将其转换成JPEGNSData实例实例。

接下来,去到imagePickerController(_:didFinishPickingMediaWithInfo)并在imageView添加以下的设置

// 1

takePictureButton.hidden=true

progressView.progress=0.0

progressView.hidden=false

activityIndicatorView.startAnimating()


uploadImage(

image, 

progress:{ [unowned self] percent in

// 2

self.progressView.setProgress(percent, animated:true)

}, 

completion:{ [unowned self]tags, colors in

// 3

self.takePictureButton.hidden=false

self.progressView.hidden=true

self.activityIndicatorView.stopAnimating()

self.tags=tags

self.colors=colors

// 4

self.performSegueWithIdentifier("ShowResults", sender:self)

})

Alamofire的所有处理都是异步的,这意味着你的UI更新也会是以异步的方式:

隐藏上传按钮,显示进度视图和活动视图。

文件上传,你调用process处理程序更新的百分比。这个进度条显示的数量变化。

completion将在上传完成时执行。这里设置控件的状态回到初始的状态。

最后Storyboard在上传成功(或失败)结束后刷新结果页面。如果发生错误用户界面将不会改变。

接下来,在ViewController.swift之上添加以下:

import Alamofire

这允许您在代码中使用Alamofire模块提供的功能。

接下来,回到uploadImage(_:progress:completion:),并添加以下代码在你的UIImage实例中:

Alamofire.upload(

.POST,

"http://api.imagga.com/v1/content", 

headers:["Authorization":"Basic xxx"], 

multipartFormData:{multipartFormData in

multipartFormData.appendBodyPart(data:imageData, name:"imagefile", 

    fileName:"image.jpg", mimeType:"image/jpeg")

}, 

encodingCompletion:{encodingResult in

}

)

确保将替换Basic xxx标题取自Imagga仪表板。在这里你将JPEG数据blob(imageData)转换成一个MIME 分段请求Imagga contentendpoint。

确保将Basic xxx替换从Imaggadashboard获取的真实的认证头。你将JPEG数据包(imageData)转换成一个MIME 分段请求Imagga 的content 端点。

接下来,添加以下encodingCompletion闭包中:

switch encodingResult{

case.Success(letupload, _, _):

   upload.progress{bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in

dispatch_async(dispatch_get_main_queue()){

   let percent = (Float(totalBytesWritten)/Float(totalBytesExpectedToWrite))

progress(percent:percent)

}

}

upload.validate()

upload.responseJSON{ response in

}

case.Failure(let encodingError):

print(encodingError)

}

这段代码调用 Alamofire 上传功能,通过在计算来更新进度条。

注意:Alamofire 并不会保证调用过程会在主队列上回调;因此你必须通过向主队列调度来更新用户界面。有一些 Alamofire 的回调,比如 responseJSON ,都会默认在主队列调用。例如:

dispatch_async(queue ?? dispatch_get_main_queue()){

let response = ... 

completionHandler(response)

}

为了改变默认的做法,你需要给 Alamofire 提供一个 dispatch_queue_t 。

接下来,添加下面的代码到upload.responseJSON:

// 1.

guard response.result.isSuccess else {

print("Error while uploading file:\(response.result.error)")

completion(tags:[String](), colors:[PhotoColor]())

return

}

// 2.

guard let responseJSON=response.result.value as?[String:AnyObject],    

uploadedFiles = responseJSON["uploaded"]as?[AnyObject], 

firstFile = uploadedFiles.first as?[String:AnyObject], 

firstFileID = firstFile["id"] as?String else {

   print("Invalid information received from service")

   completion(tags:[String](), colors:[PhotoColor]())

   return

}

print("Content uploaded with ID:\(firstFileID)")

// 3.

completion(tags:[String](), colors:[PhotoColor]())

这边将逐行解释上面的代码:

检查响应是否成功;如果不成功,输出错误信息并调用completion。

检查响应的每个部分,验证所期望的类型是收到的实际类型,如果firstFileID没有被解析,那么输出错误信息,并调用completion。

调用completion来更新 UI。你没有任何下载的标志或颜色,所以简化调用数据为空的情况。

注意:每一个响应都有一个结果,包括枚举值和类型。使用自动验证,当它返回一个合法的 HTTP 码(在200-299之间的内容类型都被认为是可以接受的)。

您可以通过添加 .validate 手动执行验证,代码如下:

Alamofire.request(.GET,"https://httpbin.org/get", parameters:["foo":"bar"])

.validate(statusCode:200..<300)

.validate(contentType:["application/json"])

.response{response in

// response handling code

}

如果你在上传期间发生错误,UI 并不会显示错误;它仅仅返回没有标记或颜色给用户。这不是最好的用户体验,但对于这个Alamofire教程也算可以了。

编译并运行您的项目,选择一个图片,看文件上传进度条的变化,上传完成后,你会看到下面的信息:

你已经成功的上传图片!


获取数据

上传图像Imagga之后,下一步是获取Imagga分析产生的照片标签。

ViewController的 extension 里面,uploadImage(_:progress:completion:)的下面添加如下的代码::

func downloadTags(contentID:String, completion:([String])->Void){

Alamofire.request(

.GET,

"http://api.imagga.com/v1/tagging",   

parameters:["content":contentID], 

  headers:["Authorization":"Basic xxx"]

)

.responseJSON{ response in

guard response.result.isSuccess else {

   print("Error while fetching tags:\(response.result.error)")

   completion([String]())

   return

}

guard let responseJSON = response.result.value as?[String:AnyObject] else {

   print("Invalid tag information received from service")

   completion([String]())

   return

}

print(responseJSON)

completion([String]())

}

}

再次强调,一定要将Basic xxx替换成实际授权xxx头。这里你将执行HTTP GET请求,设置好 URL 以及对应的参数。

接下来,回到uploadImage(_:progress:completion:)用以下代码替换成功后的回调处理:

self.downloadTags(firstFileID){ tags in

completion(tags:tags, colors:[PhotoColor]())

}

编译运行你的工程,上传一个文件,之后你在控制台就会看见返回的数据:


你不必在意这个教程中给出confidence分数,只需用到tag的名称。

接下来,回到downloadTags(_:completion:)并用以下代码替换.responseJSON里面的代码:

// 1.

guard response.result.isSuccess else {

print("Error while fetching tags:\(response.result.error)")

completion([String]())

return

}

// 2.

guard let responseJSON = response.result.value as?[String:AnyObject], 

results = responseJSON["results"] as?[AnyObject],

firstResult = results.first, 

tagsAndConfidences = firstResult["tags"]as?[[String:AnyObject]] else {

print("Invalid tag information received from the service")

completion([String]())

return

}

// 3.

let tags = tagsAndConfidences.flatMap({ dict in

return dict["tag"]as?String

})

// 4.

completion(tags)

下面是每步的代码:

检查响应是否成功;如果不成功,输出错误信息并调用 completion

对返回 json 数据进行解析。

迭代 tagsAndConfidences 数组,检索 tag.

调用 completion。

注意:你使用Swift的flatMap方法来进行迭代,这个方法在遇到值为nil的情况不会崩溃,并且会从返回结果中移除为nil的值。这可以让你使用条件解包(as?)来验证字典的值是否可以转换为一个字符串。

再一次编译运行你的工程,选择一涨图片,然后你会看到下面的界面:


非常流畅!Immaga是聪明的API。:]接下来,您将获取图像的相关颜色。

下面在ViewController的downloadTags(_:completion:)中添加以下扩展方法

func downloadColors(contentID:String, completion:([PhotoColor])->Void){

Alamofire.request(

.GET,

"http://api.imagga.com/v1/colors",   

parameters:["content":contentID,"extract_object_colors":NSNumber(int:0)],

// 1.

headers:["Authorization":"Basic xxx"]

)

.responseJSON{ response in

// 2.

guard response.result.isSuccess else {

   print("Error while fetching colors:\(response.result.error)")

   completion([PhotoColor]())

   return

}

// 3.

guard let responseJSON = response.result.value as? [String:AnyObject],       

   results = responseJSON["results"] as?[AnyObject],       

   firstResult=results.first as?[String:AnyObject],       

   info=firstResult["info"] as?[String:AnyObject],       

   imageColors=info["image_colors"] as?[[String:AnyObject]] else {

print("Invalid color information received from service")

completion([PhotoColor]())

return

}

// 4.

let photoColors = imageColors.flatMap({(dict)-> PhotoColor? in

   guardletr=dict["r"] as?String,         

   g=dict["g"] as?String,        

   b=dict["b"] as?String,         

   closestPaletteColor=dict["closest_palette_color"] as?String else{

   return nil

}

return PhotoColor(red:Int(r),         

green:Int(g),         

blue:Int(b),         

colorName:closestPaletteColor)

})

// 5.completion(photoColors)

}

}

最后,返回uploadImage(_:progress:completion:)方法,在completion里面的success的情况,添加下面的代码:

self.downloadTags(firstFileID) { tags in

self.downloadColors(firstFileID ){ colors in

completion(tags:tags, colors:colors)

}

}

再一次编译运行你的工程,选择一涨图片,然后你会看到下面的界面:


你使用RGB颜色映射到PhotoColor结构改变视图的背景颜色。您已经成功上传图像Imagga并获取数据。你已经走了很长的路,但还有一些改进的空间。

优化PhotoTagger

你可能已经注意到了,在PhotoTagger里面有重复代码。

Alamofire 提供了一个简单的方法来排除重复的代码并且提供集中配置。这就需要创建一个结构体,遵循URLRequestConvertible协议,并且更新你的上传和请求调用。

创建一个 Swift 文件,点击File\New\File…,然后在 iOS 下面选择 Swift 文件,点击下一步,文件命名为 ImaggaRouter.swift,然后点击创建。

在你新建的文件中添加下面的代码:

import Foundation

import Alamofire

public enum ImaggaRouter:URLRequestConvertible {

static let baseURLPath = "http://api.imagga.com/v1"

static let authenticationToken = "Basic xxx"

case Content

case Tags(String)

case Colors(String)

public var URLRequest:NSMutableURLRequest{

   let result:(path:String, method:Alamofire.Method, parameters:[String:AnyObject]) = {

      switch self {

      case.Content:

        return("/content", .POST,[String:AnyObject]())

      case.Tags(letcontentID):

        let params=["content":contentID]

        return("/tagging", .GET, params)

      case.Colors(letcontentID):

        let params=["content":contentID,"extract_object_colors":NSNumber(int:0)]

        return("/colors", .GET, params)

}

}()

let URL = NSURL(string:ImaggaRouter.baseURLPath)!

let URLRequest = NSMutableURLRequest(URL:URL.URLByAppendingPathComponent(result.path))

URLRequest.HTTPMethod=result.method.rawValue

URLRequest.setValue(ImaggaRouter.authenticationToken, forHTTPHeaderField:"Authorization")

URLRequest.timeoutInterval=NSTimeInterval(10*1000)

let encoding = Alamofire.ParameterEncoding.URL

return encoding.encode(URLRequest, parameters:result.parameters).0

}

}

Basic xxx替换为实际授权头,设置好 URL 以及对应的参数。这个router会帮助我们创建NSMutableURLRequest实例,并且提公布了三种情况:.Content,.Tags(String), or.Colors(String)。现在所有的模板代码都在这里,如果你需要更新它的话。

返回uploadImage(_:progress:completion:方法,并且把Alamofire.upload替换成下面的代码:

Alamofire.upload(

ImaggaRouter.Content, 

multipartFormData:{ multipartFormData in

multipartFormData.appendBodyPart(data:imageData, name:"imagefile",     

fileName:"image.jpg", mimeType:"image/jpeg")

},

/// original code continues...

然后替换downloadTags(_:completion:)方法里的Alamofire.request

Alamofire.request(ImaggaRouter.Tags(contentID))

最后,更新downloadColors(_:completion:)代码里的Alamofire.request

Alamofire.request(ImaggaRouter.Colors(contentID))

最后一次编译运行,所有的功能都像之前一样,也就意味着没有破坏你的 app,进行了代码重构。不错的工作!

深入学习

您可以下载完整的版本的这个Alamofire教程的项目。别忘了代替你的token

这Alamofire教程覆盖了非常基本的。你可以查看Alamofire官方文档深入学习https://github.com/Alamofire/Alamofire.

同样,你可以花些时间来了解更多关于苹果的NSURLSession:

Apple WWDC 2015 – 711 – Networking with NSURLSession

Apple URL Session Programming Guide

Ray Wenderlich – NSURLSession Tutorial

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容