如何在 Swift 中使用 Alamofire 进行网络编程

做者:gregg mojica,原文连接,原文日期:2015-11-30
译者:小铁匠Linus;校对:Cee;定稿:[](undefined)javascript

2014 年 6 月 Apple 发布 Swift 以来,如何在 Swift 中进行网络编程一直成为程序猿们关注的焦点。甚至,Chris Lattner,Swift 的做者之一,也发推说过,在 Swift 中解析 JSON 还有很长的路要走。所以,许多人开始寻求替代方案。尽管,在 Swift 中也有处理 JSON 解析的内建类,可是对开发者来讲并非很友好。幸运的是,Alamofire 出现了。Alamofire 是一个能够帮助咱们解析 JSON 的强有力网络库,它由 Objective-C 中同类网络库 AFNetworking 的做者编写。html

在这个又臭又长、近乎 3500 多词(译者注:in English)的教程中,咱们将探讨一系列普遍的网络基本话题,并创建一个假日待办应用。java

同时,你会从本教程中学到:如何使用和解析 JSON,如何自定义服务器端,如何使用 HerokuMongoLab 等工具,HTTP 的工做原理(包括 GET,POST 和 DELETE 请求),如何使用 git 和终端(terminal)以及如何使用 Cocoapods。若是你以为上面提到的内容太多了,那就对了,拿一杯咖啡,就让咱们开始吧。node

哦,AppCoda 的全部做者祝你们节日开心!?ios

注意:本教程是一个进阶教程,涵盖了不少东西。并且,我假设你已经对 iOS 和 Swift 有了很坚实的了解。文章中诸如 tableviews, autolayout,delegate 等话题都不会深刻的解释原理。你若是记不清这些内容,能够先去学习咱们推出的优秀课程,而后再回来看本教程。git

开始

为了实现本教程要实现的功能,我已用 Node.js 写了一个服务器后端。这里须要给那些对它不熟悉的人解释一下,Node.js 是一个基于 Javascript、运行在 Google Chrome 的 V8 引擎中的运行时环境。长话短说,总之它是一个特别可靠,速度特别快,特别厉害的东西,哈哈。github

为了搞定这个后端,我同时也使用了 Restify 和 MongoDB。MongoDB 是在 Web 开发人员中很流行的一个 no-SQL 数据库。咱们可使用 MongoDB 存储全部咱们相关的数据。mongodb

当我刚开始使用 Node 的时候,我不知道这些东西都是怎么运行的,其余我所浏览的一些博客也从没有解释 Node 究竟是怎么工做的。所以,尽管这是个 iOS 的博客,但我仍是要介绍一下 Javascript 和 Node 服务器的工做原理。数据库

我搜遍了网络,都没有一个详细的教程引导你建立一个 API 与 iOS 应用程序交互的步骤,从如今开始就有了。express

碰见 Node.js

像我以前提到的同样,Node.js 是一个很强大的服务器端开发技术,它创建在 Chrome 的运行时环境上。所以,它是高度异步的和非阻塞的(若是你不知道我说的是什么意思,其实很简单,大概就是:使用主线程或者应用的主要部分不会被阻塞)。多线程是一种能够防止延迟且能提升项目效率的编程技术。你把应用想象成一条高速公路,若是只有一条通道,却有 20 辆车要经过,那么他们就颇有可能会堵车。若是一条高速公路有三条都有出入口的通道,那么堵车的机会就很小。多线程就能够这样来理解。在一个多线程的环境里,代码执行在不一样的线程就能够避免应用阻塞,从而防止程序奔溃。

Node 是由 Joyent 开发并维持的,Joyent 是一家位于旧金山的云计算公司。

若是你仍然不清楚全部这些是怎么运行的,想一想后端具体干了些什么吧。下面列出来一些:

  1. 后端是一个为你传送 API 的地方(咱们如今正在为当前的应用构建 API,就和网络上其余的 API 同样,其中包括了咱们在以前的 tvOS 教程中使用的 forecast.io API)。

  2. MongoDB 提供了一个保存全部数据的地方。当你想要 POST 一条新的消息,咱们须要有个地方能够存储这条消息。在本教程中,咱们将把这些数据存储到 MongoDB 数据库中。

  3. 建立一个功能完整的 REST API,它遵循 REST 协议。

咱们的 MongoDB 放在 MongoLab 的主机上,Node 服务器放在 Heroku 上。Heroku 由 Salesforce 提供支持,能够做为 Node,Rails,Python等应用的主机服务商。MongoLab 也是一家能够当 MongoDB 主机的服务商。

HTTP 请求介绍

在咱们开始写代码以前,你应该了解 HTTP 请求以及如何在咱们的应用里使用。

  • GET 请求 - GET 请求会查询咱们的数据库,而后获取内容。GET 请求能够被限制,使得其只能获取一个、多个或所有的内容。事实上,每次你访问 google.com 或浏览你的 Facebook/Twitter 主页,你都会发起 GET 请求(可能你以前都不知道这个东西)!

  • POST 请求 - POST 请求会发送数据到服务器,而后保存这个数据。举个例子,当你在 Facebook 或 Twitter 上写好文字,而后按 Post/Tweet 按钮的时候,你就发起了 POST 请求。

  • UPDATE 请求 - UPDATE 请求可让你修改已经存在的内容。当你编辑一条 Facebook 消息时,其实使用到了 UPDATE 请求。

  • DELETE 请求 - DELETE 请求会删除对应的内容。当你按了删除按钮删除 Facebook 或 Twitter 消息的时候,实际上是调用了 DELETE 请求。

以上这四个请求类型是基于 REST 协议的。Internet 能运行就是由这些请求组成的。你可能也据说过 CRUD 这个缩写词,CRUD 是由 Create,Read,Update 和 Delete 的首字母组成的。很显然,这些单词就和 POST,GET,UPDATE 和 DELETE 是一一对应的。

帅气!如今咱们已经对 HTTP 协议有必定的理解了,咱们能够进入到此次教程的核心部分了。

配置必要的工具

在咱们使用 MongoLab 或 Heroku 以前,咱们应该要确保 Node.js 能正常使用。

打开 Node.js官网,根据简单的引导下载 Node 到你的电脑上。

而后,到 npm 官网下载 npm。

为了正确配置咱们的后端,咱们须要分别在 Heroku 和 MongoLab 上注册账号。咱们先从 MongoLab 开始吧,去 MongoLab 官网注册账号。

确保选择的是 single-node(免费),填上你数据库的名字。我这边取名为 alamofire-db(以 db 为后缀表示是一个数据库,这是比较广泛的命名规范)。

接下来,登陆你的数据库,定位好 MongoDB 数据库的 URI。

立刻就让你添加一个新的数据库账号,输入用户名和密码。不要忘记密码。

如今返回到你设置 URI 的页面,修改为新的地址。好比:

mongodb://<dbuser>:<dbpassword>@ds057954.mongolab.com:57954/alamofire-db

替换成:

mongodb://gregg:test@ds057954.mongolab.com:57954/alamofire-db

MongoLab 搞定!

如今去 Heroku.com,免费注册后,打开 heroku toolbelt 页面

跟随指南,成功安装后,打开终端并登陆 heroku。若是你以前从未使用过终端,不用担忧。本教程会屡次使用终端,这样你最终就会对终端的使用有一个清晰的认识。

一旦你在终端上登陆 heroku,可使用 cd 命令(cd 表明改变目录)进到对应目录,将以前从 dropbox 下载的工程文件夹移动进去。

按下回车键就能够执行这行命令了。干的不错,如今咱们能够用 git 提交(Push)东西到 heroku 了。

在终端中键入如下命令:

bash
git init
git add .
git commit -m "First Commit"

这三行命令,初始化了一个仓库(repository,简写为 repo),并添加了当前目录下的全部文件到这个仓库,最终提交并保存。

git 是一款很流行的版本控制软件。

如今你能够看终端里应该和下图的内容差很少:

由于你以前已经成功安装了 heroku toolbelt,因此你如今能够在终端里键入 heroku login,并输入账号密码。敲回车后继续,若是账号密码没问题的话,你的 Email 会以蓝绿色高亮显示。

如今,键入 heroku create 来建立一个新的 heroku 应用。Heroku 会建立一个新的带有域名的应用给你。好比,个人就是 https://whispering-plains-1537.herokuapp.com/

如今,键入 git push heroku master 来把你新建的应用发送到 heroku。

若是一切顺利的话,会显示以下图(其中的某一些设置可能会不一样)。

使用 Node.js, Express, MongoLab & Mongoose

让咱们从下载示例工程开始,连接在这里。打开你最喜欢的文本编辑器(我这边用的是 Sublime Text 2;能够在这里下载免费版,若是你支持的话也能够购买),而后继续。

Javascript 在很大程度上是和 Swift 很类似的。咱们以后会使用 express 和 mongoose 两个著名 node 包。请确保你已经在系统上安装 npm 和 node 包管理器。

Express 是 Node.js 中的一个「快速、强大而又轻量级」的网络框架,它能够轻松解决路由(Route)问题。你问什么是路由?路由就是你与网络交互的方式。每次你打开 google.com 的时候,其实你访问的是根主页,即 google.com/。假如你访问 google.com/hello,那就是另一个路由了。咱们接下来将要定义一个能访问咱们数据库的路由。

你能够从 expressjs.org 官网上学习更多关于 express 的知识。

下面是示例代码:

javascript
var express = require('express'); // 1
var app = express(); // 2
// 当一个 GET 请求访问主页的时候,会返回 hello world
app.get('/', function(req, res) { // 3
  res.send('hello world'); // 4
});

第一行代码设置了一个叫 express 的变量。第二行代码,把 express 初始化后赋值给一个叫 app 的变量。在第三行代码,app 这个变量表明了 express 环境,调用它的 get() 方法(形式相似 Swift)。当一个用户访问 / 根主页的时候,就会显示「hello world」。这是 express 做为路由的一个例子。若是须要更多信息,能够访问 express 官网查看。

如今,咱们已经配置好了 mongo 数据库的环境,接下来让咱们来使用 cURL 请求测试一下功能。cURL 是一款命令行程序,它能够发送 HTTP 请求。咱们将会先使用 cURL 作一下实验,而后再迁移到 Alamofire 去。

JavaScript 介绍

模型

打开你的文本编辑器(再次顺便说一下,我用的是 Sublime),同时打开 app.js 文件。正如你看到的,应用被分割成了一个 model 和路由文件(就是你刚打开的 app.js 文件)。model 文件能够创建模式(schema)或数据库结构。让咱们先来简单看看这个文件吧。

javascript
var mongoose = require('mongoose'),
    Schema = mongoose.Schema;
 
var TodoSchema  = new Schema(
    {
  name: String
});
 
mongoose.model('employees', TodoSchema);

咱们可使用 mongoose,它是一个用在应用与 mongo 之间做为接口的 npm 包。我起初在构建一个雇工跟踪应用,并把 model 命名为 employee,可是可能会随时修改这个 model。我保留着它,是由于这个教程的接下来部分可能会用到。

Mongoose 能很方便的提供与 mongoLab 的 heroku node 应用链接并提供相应的接口。这的确很是方便。

路由

路由文件里存的是咱们将会输出到 app.js 文件的内容。不用太担忧这个输出——它是 node 中一个比较先进的特性,也超出了本教程的范围。

注意第 26 行的 newTodo。正如你可能猜到的,这行代码建立了一个新的 todo。

javascript
var emp = new Todo(req.body);
 
    emp.save(function(err){
        if (err) {
            res.send('Error occurred');
            return console.log(err);
        }
        res.send(emp);
});

咱们把 Todo 对象(在第四行定义了一个与 mongoose 链接的对象)赋值给一个叫 emp 的变量,并设置 req.body(req 表明请求,它会发送给咱们数据,同时,res 表明回复,它会返回咱们的要返回的东西)。

随意浏览一下文件中剩下的方法。

就像粘稠的胶水——App.js

如今回到 app.js 文件,这里是整个应用的主要部分。接下来列出来一些这个文件里的重点部分(译者注:对照下图看):

  • 第 13 行代码创建 express 应用

  • 第 15 - 22 行代码配置该应用

  • 第 33 行代码使用 mongoose 将应用链接到 mongoLab 数据库

  • 第 35 行代码创建链接

  • 第 41 - 45 行代码创建应用的路由文件并链接到 /routes/todo.js 文件

  • 第 48 行代码建立服务器

以上这些,能让你了解到一些 Javascript 应用的基本运做知识。可是,毕竟这篇教程不是主讲 Javascript 的,我不会继续深究。固然,我仍是鼓励大家去研究一下 express 和 mongoose。

使用 cURL

在咱们的 node 应用开启状态下,咱们能够执行一些 cURL 请求来作测试。一旦咱们作完测试,就能够迁移到 Alamofire 上去了。

GET 请求

在终端里执行下面的代码(记得将 url 修改为你本身对应的 heroku url)。

bash
curl -i -H "Accept: application/json" "https://rocky-meadow-1164.herokuapp.com/todo"

命令行中的 -i 和 -H 参数,表示咱们将要接收什么东西。咱们会接收 JSON 并将 JSON url 追加到请求的末尾。

你应该能看到有数据返回了。和下图差很少。

正如你看到的,返回的数据就是咱们想要获得的。若是你已经将 url 替换成你本身的,你可能什么也看不到,由于你的 mongodb 里如今还没数据。

POST 请求

加入你想要加一些数据到数据库里,你须要的就是下面的 POST 命令。

bash
curl -H "Content-Type: application/json" -X POST -d '{"name":"Buy Presents"}' https://rocky-meadow-1164.herokuapp.com/todo

而后,你使用以前讲过的 GET 请求,就能够看到你刚才添加的「Buy Presents」的内容了。

DELETE 请求

bash
curl -X DELETE 'https://rocky-meadow-1164.herokuapp.com/todo/5657901fee93910900cc54ed'

很棒!接下去咱们不会讲 PUT 请求,由于在咱们这个应用里暂时还用不上。可是它和其余的请求使用起来是差很少的。

使用 Alamofire 设置 iOS 应用

让咱们重新建一个名叫 TodoApp 的 Xcode 工程开始吧。由于假期就要到来,咱们应该有一种方式来跟踪这件事情。幸运的是,咱们有咱们的 node 应用能够帮忙。

虽然你能够手动安装 Alamofire(经过拖拽源文件到对应工程的方法),可是咱们选择使用 Cocoapods。Cocoapods 是一款为 iOS 工程提供依赖管理的工具。在使用 Cocoapods 的时候,开发者能够轻松的添加框架或第三方类库。若是你以前没有使用 Cocoapods,强烈推荐你去使用。

接下来,在终端里运行如下命令能够确保你在接下来的步骤后成功安装 Cocoapods。

bash
$ gem install cocoapods

而后,经过 cd 命令进入你工程所在的目录,键入如下命令。

bash
vim Podfile

Vim 是一款系统自带的命令行编辑器,与 Sublime Text 或 TextMate 相似。咱们如今要新建一个 Podfile 的文件,Cocoapods 每次都会去这个文件里查询是否须要更新工程的 pod(包括各类的依赖)。

在 Podfile 这个文件里键入以下内容:

ruby
source 'https://github.com/CocoaPods/Specs.git'
 
platform :ios, '8.0'
 
use_frameworks!
 
pod 'Alamofire', '~> 3.0'

而后,按 ESC 键,并输入 :wq,再敲回车。其中,wq 表示保存并退出。

咱们如今已经成功建立 Podfile 而且保存了,为了安装 CocoaPods,在终端里输入如下命令:

bash
pod install

敲了回车后,若是一切都设置好的话,大概会呈现下图显示的内容。

这时候,你能够看到命令行里要求你关闭当前打开的 Xcode 而且之后都用 .xcworkspace 为后缀的文件来打开工程。

下面这个命令可以很是方便地打开当前目录的 finder 界面。到此为止咱们在 Terminal 中的操做就那么多,看上去一天以内有那么多就够了!

bash
open .

打开 ViewController.swift,让咱们继续吧。

Alamofire GET 请求

在打开的 ViewController.swift 里,输入如下代码来导入 Alamofire:

import Alamofire

在 viewDidLoad() 方法里键入如下代码来使用 Alamofire。

Alamofire.request(.GET, "https://rocky-meadow-1164.herokuapp.com/todo") .responseJSON { response in // 1
      print(response.request)  // original URL request
      print(response.response) // URL response
      print(response.data)     // server data
      print(response.result)   // result of response serialization

      if let JSON = response.result.value {
         print("JSON: \(JSON)")
        }
}

在第一行代码中,咱们声明了一个 GET 请求,而且传入了一个咱们须要的 URL。运行当前的应用,看看返回的是什么。若是一切都设置正确的话,你会看到返回的是 JSON 数据。

如今,打开 Main.storyboard,添加一个 tableview 到 view controller,并将视图控制器嵌入到 navigation controller。你的 storyboard 如今看起来应该跟个人同样,以下图(值得注意的是,如今返回的 JSON 数据还只是显示在控制台上,咱们要将其显示出来。)。

将如下代码复制并粘帖到你的 Viewcontroller.swift 文件里。

import UIKit
import Alamofire
class ViewController: UIViewController {
 
    @IBOutlet weak var tableView: UITableView!
    var jsonArray:NSMutableArray?
    var newArray: Array<String> = []
 
    override func viewDidLoad() {
        super.viewDidLoad()
        
        Alamofire.request(.GET, "https://rocky-meadow-1164.herokuapp.com/todo") .responseJSON { response in
                print(response.request)  // original URL request
                print(response.response) // URL response
                print(response.data)     // server data
                print(response.result)   // result of response serialization

                if let JSON = response.result.value {
                    self.jsonArray = JSON as? NSMutableArray
                    for item in self.jsonArray! {
                        print(item["name"]!)
                        let string = item["name"]!
                        print("String is \(string!)")

                        self.newArray.append(string! as! String)
                    }

                    print("New array is \(self.newArray)")

                    self.tableView.reloadData()
                }
        }



        // Do any additional setup after loading the view, typically from a nib.
    }
}

我初始化了两个数组 jsonArray 和 newArray,用 for 循环遍历了返回数据的那个 jsonArray,将其中的每一个数据保存到 newArray 中。

我使用 POST cURL 请求在数据库里多添加了一些数据。用法相似,再也不赘述。

你能够试试下面代码演示的 GET 请求的极致精简写法。

Alamofire.request(.GET, "https://rocky-meadow-1164.herokuapp.com/todo").responseJSON { response in debugPrint(response) }

接下来,在文件顶部的 UIViewController 定义后面添加 UITableViewDelegate 和 UITableViewDataSource。而且,在 viewDidLoad() 方法里键入以下代码:

self.tableView.dataSource = self
self.tableView.delegate = self

最后,添加 UITableView 的 delegate 方法。

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.newArray.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

    cell.textLabel?.text = self.newArray[indexPath.row]
    return cell
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

正如你看到的,咱们的 tableview 已经成功显示数据了。

如今,让咱们来添加一个按钮,用来添加数据到列表中。首先,先在 storyboard 里添加一个叫 AddViewController 的类,并用 segue 的方式链接起来。你的 storyboard 应该和下图差很少。

Alamofire POST 请求

在你的 AddViewController.swift 文件里,为 textfield 创建一个 IBOutlet(命名为 textView)和为 Save 按钮创建一个 IBAction。在 Save 按钮代码下面键入以下代码:

Alamofire.request(.POST, "https://rocky-meadow-1164.herokuapp.com/todo", parameters: ["name": self.textView.text!])

self.navigationController!.popViewControllerAnimated(true)

正如你看到的,Alamofire 大大简化了发送 POST 请求的过程。

接下来,咱们来对 ViewController.swift 文件进行重构,确保咱们在保存数据后能及时更新列表。删除 viewDidLoad() 方法里 GET Alamofire 的代码,用如下的 downloadAndUpdate 方法代替。

func downloadAndUpdate() {
        Alamofire.request(.GET, "https://rocky-meadow-1164.herokuapp.com/todo") .responseJSON { response in
            print(response.request)  // original URL request
            print(response.response) // URL response
            print(response.data)     // server data
            print(response.result)   // result of response serialization

            if let JSON = response.result.value {
                self.jsonArray = JSON as? NSMutableArray
                for item in self.jsonArray! {
                    print(item["name"]!)
                    let string = item["name"]!
                    print("String is \(string!)")

                    self.newArray.append(string! as! String)
                }
                print("New array is \(self.newArray)")
                self.tableView.reloadData()
            }
        }

    }

如今,在 viewWillAppear() 方法里调用这个方法,以下。

override func viewWillAppear(animated: Bool) {
   self.downloadAndUpdate()
}

若是你再次编译并运行这个应用,就会发现每次添加新的 todo 后都会从新加载。可是,这是为何呢?

这就关系到 view controller 的生命周期,这里我就简短讨论一下。viewDidLoad() 会在 view 初始化后而且全部控件都结束加载后被调用。问题就出在,当你从已经加载的 ViewController 上加载另一个 view(好比 AppViewController)时,viewDidLoad 方法不会被调用(以前已经初始化过)。viewWillAppear 方法会在每次 view 在屏幕上显示时调用。由于咱们须要在再次显示 ViewController.swift 时候显示,因此这个方法恰好可用。

Alamofire DELETE 请求

如今在刚才的 newArray 下面添加一个 IDArray。

var IDArray: Array<String> = []

接下来,更新 downloadAndUpdate 方法的相应部分,代码以下。

self.newArray.removeAll() // NEW
self.IDArray.removeAll() // NEW

Alamofire.request(.GET, "https://rocky-meadow-1164.herokuapp.com/todo") .responseJSON { response in
            print(response.request)  // original URL request
            print(response.response) // URL response
            print(response.data)     // server data
            print(response.result)   // result of response serialization

            if let JSON = response.result.value {
                self.jsonArray = JSON as? NSMutableArray
                for item in self.jsonArray! {
                    print(item["name"]!)
                    let string = item["name"]!
                    let ID = item["_id"]! // NEW

                    self.newArray.append(string! as! String)
                    self.IDArray.append(ID! as! String) // NEW
                }
                print("New array is \(self.newArray)")
                self.tableView.reloadData()
            }
        }

两行带有 NEW 注释的代码是新添加的。从代码的本质上来讲,咱们在循环中得到对应的 ID 并保存到数组 IDArray 中。一样,咱们也须要将不须要的数据从列表中删除并重置。

添加 commitEditingStyle 方法,以调用 DELETE 请求来删除对应的不须要数据。

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            print("ID is \(self.IDArray[indexPath.row])")

            Alamofire.request(.DELETE, "https://rocky-meadow-1164.herokuapp.com/todo/\(self.IDArray[indexPath.row])")
            self.downloadAndUpdate()
            
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }
    }

正如你看到的,以上代码遵循了咱们应用的 API,即经过传入 /todo/ID 来调用 DELETE 请求删除对应的数据。

同时,咱们用比较简单的 Alamofire 方法来调用 DELETE 请求并删除了对应的数据。

至此,你如今已经拥有了一个功能完备的 todo 应用了。所以,让咱们来总结一下本次教程吧。

小结

本教程探索了不少东西。从 Javascript 的 node 到 express,从 MongoDB 到 cURL,从终端到 Cocoapods,以及最后的 Alamofire,咱们深刻了解了 REST API 的建立过程和网络的工做流程。你经过本次教程应该已经坚实的掌握了如下内容:

  • 构建 API

  • 部署 API

  • 写客户端应用

  • 使用 HTTP 请求

  • 使用 Cocoapods

  • 使用 cURL

  • 使用 node 和 MongoDB/Express

  • 使用 Express 作路由

  • 使用 Alamofire

这真是一个大教程,我感谢你坚持和我走到这里。全部的源代码能够在这里下载,其中包含了 node 应用和 iOS 应用。

有任何问题和想法均可以在教程下面留言评论。下次见!

本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 http://swift.gg

相关文章
相关标签/搜索