原文 Beginning Alamofire Tutorial

原文作者 Essan Parto
译者 星夜暮晨(QQ:412027805)

http://www.jianshu.com/p/f1208b5e42d9

CocoaChina 对应地址:
http://www.cocoachina.com/ios/20141202/10390.html

2014年12月1日

2015年4月15日,更新至Xcode6.3及Swift1.2

感谢nevermosby对本文的遗漏之处进行指正,同时也感谢SwiftLanguage的推荐


学习如何使用 Alamofire 来轻松地实现 Swift 的网络请求处理

AFNetworking是 iOS 和 OS X 上最受欢迎的第三方库之一。它曾在我们的2012年的读者评选中荣获2012年度最佳 iOS 库称号。它同样也在 Github 上面获得了14000多个 stars 和4000多个 forks,是使用最广的开源项目之一。

最近,AFNetworking 的作者 Mattt Thompson提交了一个新的类似于 AFNetworking 的网络 基础库,并且是专门使用最新的 Swift 语言来编写的,名为:Alamofire

AFNetwork 的前缀 AF 是 Alamofire 的缩写,因此这个新的库名称其实是根据 Swift 的约定来进行命名的。

在本教程的第一部分中,我们将带领大家使用 Alamofire 来制作一个图片库应用,其来源是500px.com。在这个过程中,您可以学习到如何使用 Alamofire 中的重要组件,以及了解在应用中处理网络请求的某些重要的知识点。

本教程的第二部分将基于第一部分所制作的应用,并为其增加一些好用的功能。您可以通过这个过程学习到更多高级的 Alamofire 用法。

本教程假定您已熟悉 Swift 语言以及 iOS 开发。如果不是的话,请参阅我们的其他教程。还有,本教程使用 Xcode 6.3 作为开发环境。

提示:

如果您已经熟悉了 Alamofire 的基本用法,那么您可以直接跳到本文的第二部分。但是请确保您已拥有消费者密钥(cunsumer key),然后参照下文在应用中替换它。

让我们开始吧

首先下载本次教程的初始项目。这个项目中提供了在本教程中需要的全部 UI。这有助于您能够将注意力集中到学习 Alamofire 的使用上来,而不是花费大量时间来研究 UI。

在 Xcode 中打开这个项目,并定位到Main.storyboard文件:

我们应用的主屏幕使用UITabBarController这个常用的 UI 样式。我们的标签控制器中包含有两个标签,每个标签都有它们自己的UINavigationController页面。第一个标签让用户浏览热门图片。第二个标签让用户浏览他们已保存的文件。两个标签都使用UICollectionViewController来向用户展示图片。故事板中同样也包含了两个独立的视图控制器,在接下来的教程中我们将会用到它们。

生成并运行该应用,您首先会看到一个不停在加载的加载控件:

这看起来一点也不高端大气上档次……可以说没什么可看的。但是很快我们将会借助 Alamofire 来让他逼格高起来。

提示:

如果您很熟悉 AFNetworking 的使用,那么您可能会期待下一节我们谈论 CocoaPods相关用法。借助CocoaPods 0.36及以上版本,您便可以在iOS 8.0以上环境中使用Swift的内置框架了。

在本教程当中,我们将会使用另外一个不需要CocoaPods的方法来导入Alamofire。当然,如果您对CocoaPods很熟悉,那么也可以尽情地使用它来导入,这两者基本没有任何区别。

要获取最新版本的 Alamofire,请前往https://github.com/Alamofire/Alamofire然后单击网页右边的Download ZIP按钮。接着在 Finder 中打开起始项目文件夹,然后将Alamofire-master 文件夹拖入到您的主项目文件夹中。

打开Alamofire-master文件夹(现在它位于您的项目文件夹中),然后将Alamofire.xcodeprij文件(注意是蓝色图标!不是白色图标!)直接拖进 Xcode 中的 Photomania 项目下面,如下图所示:

接下来,单击Photomania项目,进入 General 选项卡。向下滚动到Embedded Binaries项,然后单击其下方的 + 号。选择Alamofire.framework,最后点击Add完成添加。

生成并运行您的项目以确保没有任何错误出现,然后就可以进入到下一节内容了。

使用 Alamofire 来检索数据

您可能会觉得,为什么我们要使用 Alamofire 呢?明明苹果已经提供了NSURLSession类以及其相关类,以便让我们通过 HTTP 来下载相应内容。为什么我们还要伤精费神地去使用第三方库呢?

简单来说,Alamofire 其实是基于NSURLSession的,但是它可以免去您写样板(boilerplate)代码的麻烦,并且可以让网络模块的代码更为简单易用。您可以通过一些非常简单的操作来访问 Internet 上的数据,并且写出来的代码也会更加清晰明了、简单易读。

要使用 Alamofire 的话,首先需要导入它。请打开PhotoBrowserCollectionViewController.swift文件,然后在文件顶部添加如下代码:

import Alamofire

您需要在每个使用了 Alamofire 类以及函数的文件中添加这条import语句。

接下来,在setupView()下方的viewDidLoad()方法中添加如下代码:

Alamofire.request(.GET, "https://api.500px.com/v1/photos").responseJSON() {
(_, _, data, _) in
println(data)
}

过会儿我会对其做出详细解释,但是首先您需要生成并运行该应用,这个时候您会在控制台中看到如下信息:

Optional({
error = "Consumer key missing.";
status = 401;
})

您可能不明白它说了什么鬼,不过实际上您已经成功地使用 Alamofire 来实现网络请求了!您向 Internet 上的资源发出了一个请求,然后返回了一个JSON 数据。

下面来解释一下那些代码到底做了些什么:

  • Alamofire.request(_:_)接收两个参数:methodURLString。其中,method 通常是.GET.POSTURLString通常是您想要访问的内容的 URL。其将返回一个Alamofire.Request对象。
  • 通常情况下,您只需将请求对象链接到响应方法上。例如,在上面的代码中,请求对象简单地调用了responseJSON()方法。当网络请求完毕后,responseJSON()方法会调用我们所提供的闭包。在我们的示例中,我们只是简单的将经过解析的 JSON 输出到控制台中。
  • 调用responseJSON方法意味着您期望获得一个 JSON 数据。在我们的示例中,Alamofire 试图解析响应数据并返回一个 JSON 对象。或者,您可以使用responsePropertyList来请求获得一个属性列表,也可以使用responseString来请求获得一个初始字符串。在本教程后面,您将了解更多关于响应序列化方法的使用方式。

您可以从控制台中看到输出的响应数据,服务器报告您需要一个名为consumer key的东西。在我们继续使用 Alamofire 之前,我们需要从 500px 网站的 API 中获取一个密钥。

获取消费者密钥

前往https://500px.com/signup,然后使用您的邮箱免费注册,或者使用您的 Facebook 、Twitter 或者 Google 帐号登录。

一旦您完成了注册流程,那么前往https://500px.com/settings/applications并单击"Register your application"。

您会看到如下所示的对话框:

红色大箭头指向的那些文本框里面的内容都是必填的。使用Alamofire Tutorial作为Application Name,然后使用iOS App作为Description。目前您的应用还没有Application URL,但是您可以随意输一个有效的网址来完成应用注册,您可以使用raywenderlich.com^_^。

最后,在Developer’s Email中输入您的邮箱地址,然后单击复选框来接受使用协议。

接着,单击 Register按钮,您会看到一个如下所示的框:

单击See application details链接,然后它会弹出详细信息,这时候您就可以定义您的消费者密钥了,如下所示:

从该页面中复制出您的消费者密钥,然后返回 Xcode,然后用如下代码替换掉目前为止您唯一添加的代码:

Alamofire.request(.GET, "https://api.500px.com/v1/photos", parameters: ["consumer_key": "PASTE_YOUR_CONSUMER_KEY_HERE"]).responseJSON() {
(_, _, JSON, _) in
println(JSON)
}

请确保您已经用复制的消费者密钥来替换掉PASTE_YOUR_CONSUMER_KEY_HERE

生成并运行您的应用,这时您会在控制台中看见海量的输出:

上述所有的输出意味着您成功地下载到了含有一些照片信息的 JSON信息。

JSON 数据中包含了一些图片集的属性、一些页面信息,以及一个图片数组。这里是我得到的搜索结果(您的可能会略有不同):

{
"feature": "popular",
"filters": {
"category": false,
"exclude": false
},
"current_page": 1,
"total_pages": 250,
"total_items": 5000,
"photos": [
{
"id": 4910421,
"name": "Orange or lemon",
"description": "",
.
.
.
}
},
{
"id": 4905955,
"name": "R E S I G N E D",
"description": "From the past of Tagus River, we have History and memories, some of them abandoned and disclaimed in their margins ...",
.
.
.
}
]
}

现在我们已经拥有了 JSON 数据,接下来我们就可以好好地利用它了。

使用如下代码替换掉viewDidLoad()中的 println(JSON)方法:

let photoInfos = (JSON!.valueForKey("photos") as! [NSDictionary]).filter({
($0["nsfw"] as! Bool) == false
}).map {
PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String)
} self.photos.addObjectsFromArray(photoInfos) self.collectionView!.reloadData()

上述代码将 JSON 数据转变为了更易于管理的PhotoInfo数组对象。这些对象只是简化掉了图片 ID 和 URL 属性的存储桶(bucket)。您同样可以发现代码过滤掉了一些……呃……您不希望出现的一些图片。

上述代码同样也重新加载了集合视图。初始项目的示例代码基于我们刚刚填充的photos,来创建集合视图的单元。

生成并运行您的应用,这时加载控件加载一会儿便消失。如果您仔细观察的话,您会发现一堆灰黑色的方形单元格:

离我们的目标越来越接近了,加油!

我们仍然选择PhotoBrowserCollectionViewController.swift文件,在collectionView(_: cellForItemAtIndexPath:)方法中的return cell前加上如下的代码:

let imageURL = (photos.objectAtIndex(indexPath.row) as! PhotoInfo).url

Alamofire.request(.GET, imageURL).response() {
(_, _, data, _) in let image = UIImage(data: data! as! NSData)
cell.imageView.image = image
}

上述的代码为photos数组中的图片创建了另外的 Alamofire 请求。由于这是一个图片请求,因此我们使用的是一个简单的request方法,其在NSData 的blob 中返回响应。接下来我们直接把数据放入到一个UIImage的实例中,然后反过来将实例放入早已存在于示例项目中的imageView 当中。

再一次生成并运行您的应用,这时应当出现一个图片集合,与下图相似:

对于 Alamofire 的工作效果想必您现在已经心中有数,但是您不会想在每次从服务器请求数据的时候,要不停的复制、粘贴 API 地址,以及添加消费者密钥。除了这一点非常让人不爽外,如果 API 地址发生了改变,那么您可能不得不再次创建一个新的消费者密钥。

幸运的是,Alamofire对于这个问题有着良好的解决方案。

创建请求路由

打开Five100px.swift,然后找到struct Five100px,其中定义了enum ImageSize。这是一个简单的基于 500px.com 的 API 文件的结构体。

在使用 Alamofire 之前,您需要在文件顶部添加下述的必要声明:

import Alamofire

现在,在struct Five100px中的enum ImageSize代码段上方添加下述代码:

enum Router: URLRequestConvertible {
static let baseURLString = "https://api.500px.com/v1"
static let consumerKey = "PASTE_YOUR_CONSUMER_KEY_HERE" case PopularPhotos(Int)
case PhotoInfo(Int, ImageSize)
case Comments(Int, Int) var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case .PopularPhotos (let page):
let params = ["consumer_key": Router.consumerKey, "page": "\(page)", "feature": "popular", "rpp": "50", "include_store": "store_download", "include_states": "votes"]
return ("/photos", params)
case .PhotoInfo(let photoID, let imageSize):
var params = ["consumer_key": Router.consumerKey, "image_size": "\(imageSize.rawValue)"]
return ("/photos/\(photoID)", params)
case .Comments(let photoID, let commentsPage):
var params = ["consumer_key": Router.consumerKey, "comments": "1", "comments_page": "\(commentsPage)"]
return ("/photos/\(photoID)/comments", params)
}
}() let URL = NSURL(string: Router.baseURLString)
let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: parameters).0
}
}

这就是我们所创建的路由,它为我们的 API 调用方法创建合适的URLString实例。它是一个简单的遵守URLRequestConertible协议的enum类型,这个协议是在 Alamofire 当中定义的。当有枚举类型采用该协议的时候,该类型就必须含有一个名为URLRequestNSURLRequest类型变量。

这个路由含有两个静态常量:API 的baseURLString以及consumerKey。(最后一次声明,请PASTE_YOUR_CONSUMER_KEY_HERE替换为您自己的消费者密钥)现在,这个路由可以在必要的时候向最终的URLString中添加消费者密钥。

您的应用拥有三个 API 终点(endpoint):一个用来取出热门照片列表,一个用来取出某个特定照片的具体信息,一个用来取出某个照片的评论。路由将会借助三个相应的case声明来处理这三个终结点,每个终结点都会接收一到两个参数。

我们已经定义了var URLRequest: NSURLRequest作为计算(computed)属性。这意味着每次我们使用enum的时候,它都会构造出基于特定case和其参数的最终 URL。

这里有一个示例代码片段,说明了上述的逻辑关系:

Five100px.Router.PhotoInfo(10000, Five100px.ImageSize.Large)
// URL: https://api.500px.com/v1/photos/10000?consumer_key=xxxxxx&image_size=4
// https://api.500px.com/v1 + /photos/10000 + ?consumer_key=xxxxxx&image_size=4
// = baseURLString + path + encoded parameters

在上面的示例中,代码路由通过照片信息 API 的终结点来寻找一个 ID 为10000的大尺寸图片。注释行将 URL 的结构进行了拆分。在这个示例中,URL 由三个部分组成:baseURLStringpath(?前面的那一部分)以及[String: AnyObject]字典,其中包含有传递给 API 终结点的参数。

对于path来说,返回元组的第一个元素可以用以下的字符串形式返回:

"/photos/\(photoID)" // "/photos/10000"

和响应解析类似,请求参数可以被编码为 JSON、属性列表或者是字符串。通常情况下使用简单的字符串参数,和上面我们所做的类似。

如果您打算在您自己的项目中使用路由,您必须对它的运行机制相当熟悉。为此,请尝试搞清楚要如何构造出以下的 URL:

https://api.foursquare.com/v2/users/{USER_ID}/lists?v=20131016&group=created

您是怎么做的呢?如果您不是百分百确定答案,请花一点时间来分析下面的代码,直到您完全搞明白其工作原理:

解决方案:

static let baseURLString = "https://api.foursquare.com/v2"

case UserLists(Int)

var URLRequest: NSURLRequest {
let (path: String, parameters: [String: AnyObject]) = {
switch self {
case . UserLists (let userID):
let params = ["v": "20131016", "group": "created"]
return ("/users/\(userID)/lists", params)
}
}()
.
.
.

这里您需要为枚举添加其他的 case,比如说用户列表,它们都设置有合适的参数和路径。

加载更多图片

好的,现在应用目前显示的照片只有一个页面,但是我们想要浏览更多照片以找到我们心仪的内容。多多益善,对吧?

打开PhotoBrowserCollectionViewController.swift,然后在let refreshControl = UIRefreshControl()语句下方添加如下代码:

var populatingPhotos = false
var currentPage = 1

这里我们定义了两个变量,来记录当前是否在更新照片,以及当前我们正在浏览的是哪一个照片页面。

接下来,用以下代码替换当前viewDidLoad()的声明:

override func viewDidLoad() {
super.viewDidLoad() setupView() populatePhotos()
}

这里我们用populatePhotos()函数来替换了先前的 Alamofire 请求。之后我们就要实现populatePhotos()函数的声明。

同样的,在handleRefresh()上方添加两个函数,如下所述:

// 1
override func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y + view.frame.size.height > scrollView.contentSize.height * 0.8 {
populatePhotos()
}
} func populatePhotos() {
// 2
if populatingPhotos {
return
} populatingPhotos = true // 3
Alamofire.request(Five100px.Router.PopularPhotos(self.currentPage)).responseJSON() {
(_, _, JSON, error) in if error == nil {
// 4
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
// 5, 6, 7
let photoInfos = ((JSON as! NSDictionary).valueForKey("photos") as! [NSDictionary]).filter({ ($0["nsfw"] as! Bool) == false }).map { PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String) } // 8
let lastItem = self.photos.count
// 9
self.photos.addObjectsFromArray(photoInfos) // 10
let indexPaths = (lastItem..<self.photos.count).map { NSIndexPath(forItem: $0, inSection: 0) } // 11
dispatch_async(dispatch_get_main_queue()) {
self.collectionView!.insertItemsAtIndexPaths(indexPaths)
} self.currentPage++
}
}
self.populatingPhotos = false
}
}

啊……好长一段代码,对吧?下面是对每个注释部分的详细解释:

  1. 一旦您滚动超过了 80% 的页面,那么scrollViewDidScroll()方法将会加载更多的图片。
  2. populatePhotos()方法在currentPage当中加载图片,并且使用populatingPhotos作为标记,以防止还在加载当前界面时加载下一个页面。
  3. 这里我们首次使用了我们创建的路由。只需将页数传递进去,它将为该页面构造 URL 字符串。500px.com 网站在每次 API 调用后返回大约50张图片,因此您需要为下一批照片的显示再次调用路由。
  4. 要注意,.responseJSON()后面的代码块:completion handler(完成处理方法)必须在主线程运行。如果您正在执行其他的长期运行操作,比如说调用 API,那么您必须使用 GCD 来将您的代码调度到另一个队列运行。在本示例中,我们使用DISPATCH_QUEUE_PRIORITY_HIGH来运行这个操作。
  5. 您可能会关心 JSON 数据中的photos关键字,其位于数组中的字典中。每个字典都包含有一张图片的信息。
  6. 我们使用 Swift 的filter函数来过滤掉 NSFW 图片(Not Safe For Work)
  7. map函数接收了一个闭包,然后返回一个PhotoInfo对象的数组。这个类是在Five100px.swift当中定义的。如果您查看这个类的源码,那么就可以看到它重写了isEqualhash这两个方法。这两个方法都是用一个整型的 id 属性,因此排序和唯一化(uniquing)PhotoInfo对象仍会是一个比较快的操作
  8. 接下来我们会在添加新的数据前存储图片的当前数量,使用它来更新collectionView
  9. 如果有人在我们滚动前向 500px.com 网站上传了新的图片,那么您所获得的新的一批照片将可能会包含一部分已下载的图片。这就是为什么我们定义var photos = NSMutableOrderedSet()为一个组。由于组内的项目必须唯一,因此重复的图片不会再次出现
  10. 这里我们创建了一个NSIndexPath对象的数组,并将其插入到collectionView
  11. 在集合视图中插入项目,请在主队列中完成该操作,因为所有的 UIKit 操作都必须运行在主队列中

生成并运行您的应用,然后缓慢向下滑动图片。您可以看到新的图片将持续加载:

不断加快滑动的速度,注意到问题没有?对的,滚动操作不是很稳定,有些许迟钝的感觉。这并不是我们想要提供给用户的体验,但是我们在下一节中就可以修正这个问题了。

创建自定义响应序列化方法(Serializer)

您已经看到,我们在 Alamofire 中使用所提供的 JSON、字符串,以及属性列表序列化方法是一件非常简单的事情。但是有些时候,您可能会想要创建自己的自定义相应序列化。例如,您可以写一个响应序列化方法来直接接收UIIMage,而不是将UIImage转化为NSData来接收。

在本节中,您将学习如何创建自定义响应序列化方法。

打开Five100px.swift,然后在靠近文件顶部的地方,也就是import Alamofire语句下面添加如下代码:

extension Alamofire.Request {
class func imageResponseSerializer() -> Serializer {
return { request, response, data in
if data == nil {
return (nil, nil)
} let image = UIImage(data: data!, scale: UIScreen.mainScreen().scale) return (image, nil)
}
} func responseImage(completionHandler: (NSURLRequest, NSHTTPURLResponse?, UIImage?, NSError?) -> Void) -> Self {
return response(serializer: Request.imageResponseSerializer(), completionHandler: { (request, response, image, error) in
completionHandler(request, response, image as? UIImage, error)
})
}
}

要创建一个新的响应序列化方法,我们首先应当需要一个类方法,其返回Serializer闭包(比如说上面所写的imageResponseSerializer())。这个闭包是 Alamofire 中的一个别名,其接收三个参数并返回所示的两个参数,如下所示:

public typealias Serializer = (NSURLRequest, NSHTTPURLResponse?, NSData?) -> (AnyObject?, NSError?)

类方法(例如imageResponseSerializer())接收底层的NSURLSession请求以及和响应对象一起的基本NSData数据实现方法(从服务器传来的),来作为参数。该方法接下来使用这些对象来序列化,并将其输入到一个有意义的数据结构中,然后将其从方法中返回,它同样也会返回在这个过程中发生的错误。在我们的示例中,我们使用UIImage来将数据转化为图片对象。

通常情况下,当您创建了一个响应序列化方法后,您可能还会向创建一个新的响应处理方法来对其进行处理,并让其更加易用。我们使用.responseImage()方法来完成这项任务。这个方法的操作很简单:它使用completionHandler,一个以闭包形式的代码块。一旦我们从服务器中序列化了数据,那么这个代码块将会运行。我们所需要做的就是在响应处理方法中调用 Alamofire 自己的通用.response()响应处理方法。

让我们开始让它工作起来。打开PhotoBrowserCollectionViewController.swift,然后在PhotoBrowserCollectionViewCell中的imageView属性下面,添加如下一个属性:

var request: Alamofire.Request?

这个属性会为这个单元存储 Alamofire 的请求来加载图片

现在将collectionView(_: cellForItemAtIndexPath:)的内容替换为下面所示的代码:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as! PhotoBrowserCollectionViewCell

let imageURL = (photos.objectAtIndex(indexPath.row) as! PhotoInfo).url

cell.imageView.image = nil
cell.request?.cancel() cell.request = Alamofire.request(.GET, imageURL).responseImage() {
(request, _, image, error) in
if error == nil && image != nil {
cell.imageView.image = image
}
} return cell

生成并运行您的应用,再次滚动图片,您会发现滚动变得流畅了。

为什么会流畅呢?

那么我们到底做了些什么来让滚动变得流畅了呢?其关键就是collectionView(_: cellForItemAtIndexPath:)中的代码。但是在我们解释这段代码之前,我们需要向您解释网络调用的异步性。

Alamofire 的网络调用是异步请求方式。这意味着提交网络请求不会阻止剩余代码的执行。网络请求可能会执行很长时间才能得到返回结果,但是您不会希望在等待图片下载的时候 UI 被冻结。

也就是说,实现异步请求是一个极大的挑战。如果在发出请求之后到从服务器接收到响应的这段时间中,UI 发生了改变的话怎么办?

例如,UICollectionView拥有内部的单元出列机制。创建新的单元对系统来说开销很大,因此集合视图将重用不在屏幕上显示的现有单元,而不是不停创建新的单元。

这意味着同一个单元对象,会被不停地重复使用。因此在发出 Alamofire 请求之后到接收到图片信息响应之前,用户将单元滚动出屏幕并且删除图片的操作将成为可能。单元可能会出列,并且准备显示另一个图片。

在上述的代码中,我们完成了两件事来解决这个问题。第一件事是,当一个单元出列后,我们通过设值为nil的方法来清除图片。这个操作确保我们不会显示原先的图片;第二件事是,我们的请求完成处理方法将检查单元的 URL 是否和请求的 URL 相等。如果不相等的话,显然单元已经拥有了另外的图片,那么完成处理方法将不会浪费其生命周期来为单元设置错误的图片。

接下来该何去何从?

您可以在这里下载本教程第一部分的最终版本项目。

提示:

如果您打算直接使用上面的的最终版本,那么千万不要忘记在前面的教程中所说的,用您的消费者密钥酌情替换Five100px.swift中的响应内容。

本教程介绍了相当多的内容,现在您可以好好的休息一下了!现在,多亏了 Alamofire,您的应用拥有了基本的照片浏览功能。

在我们的学习过程中,您已经学会了如何使用 Alamofire 发送 GET 请求、传递参数、创建请求路由,甚至学会了创建您自己的响应序列化方法。

在本教程的第二部分,您将会增加以下功能:

  • 照片查看器
  • 查看评论以及其他信息的能力
  • 下载照片的选项,附带有一个圆列进度条
  • 下拉刷新操作

我们希望您能够喜欢我们这部分的教程,并且能够加入我们的第二部分的教程。如果您对 Alamofire 有任何看法或建议,快来加入我们的讨论吧!

Alamofire网络库基础教程的更多相关文章

  1. Alamofire网络库进阶教程

    本章节由CocoaChina翻译组成员星夜暮晨(博客)翻译自raywenderlich:Intermediate Alamofire Tutorial,敬请勘误. 欢迎回到我们的 Alamofire ...

  2. jstl标签库基础教程及其使用代码(一)。

    概述 在 JSP 页面中,使用标签库代替传统的 Java 片段语言来实现页面的显示逻辑已经不是新技术了,然而,由自定义标签很容易造成重复定义和非标准的实现.鉴于此,出现了 JSTL ( JSP Sta ...

  3. jstl标签库基础教程及其使用代码

    概述 在 JSP 页面中,使用标签库代替传统的 Java 片段语言来实现页面的显示逻辑已经不是新技术了,然而,由自定义标签很容易造成重复定义和非标准的实现.鉴于此,出现了 JSTL ( JSP Sta ...

  4. 专注于HTTP的高性能高易用性网络库:Fslib.network库

    博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...

  5. handy网络库源码阅读

    简洁易用的C++11网络库,From:https://github.com/yedf/handy 在整理过去的资料过程中,发现过去有关注过这一个网络库,简单看了一下属于轻量级的实现,因此本文将对该库进 ...

  6. Python 基础教程 —— 网络爬虫入门篇

    前言 Python 是一种解释型.面向对象.动态数据类型的高级程序设计语言,它由 Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 1991 年.自面世以后,Pytho ...

  7. Python 基础教程 —— Pandas 库常用方法实例说明

    目录 1. 常用方法 pandas.Series 2. pandas.DataFrame ([data],[index])   根据行建立数据 3. pandas.DataFrame ({dic})  ...

  8. 网络库Alamofire使用方法学习笔记

    Github地址 由于Alamofire是swift网络库,所以,以下的所有介绍均基于swift项目 导入Alamofire 以下为使用cocoapods导入,其余的方式请参考官网 source 'h ...

  9. 【RL-TCPnet网络教程】第2章 嵌入式网络协议栈基础知识

    第2章        嵌入式网络协议栈基础知识 本章教程为大家介绍嵌入式网络协议栈基础知识,本章先让大家有一个全面的认识,后面章节中会为大家逐一讲解用到的协议. 基础知识整理自百度百科,wiki百科等 ...

随机推荐

  1. Centos 5.5 更新网卡驱动 bnx2 version: 2.0.2

    操作系统:CentOS release 5.5 (Final) 故障现象:网卡无故自动down掉,使用service  network restart 重启后没多久又会自动down , 连接数大概在2 ...

  2. 移动M站建设

    电商总结(五)移动M站建设   最近在一直在搞M站,也就是移动web站点.由于是第一次,也遇到了很多问题,所以把最近了解到的东西总结总结.聊一聊什么是移动M站,它有啥作用和优势. 也有人会问,M站和A ...

  3. NOJ1184 失落的邮票 哈希表

    意甲冠军 我们共收集N邮票.现在失去了2张,剩下N-2张-..原集邮收集了所有对.因此,找到什么两枚邮票是一个.它们输出. (确定缺少邮票是不一样的) 思路 由于编号比較大,能够用hash表压缩成数组 ...

  4. dell N5010

    Inspiron N5010Microsoft Windows 10 企业版 (64位) (英特尔)Intel(R) Core(TM) i3 CPU       M 370  @ 2.40GHz(24 ...

  5. MySQL的一些基本操作

    近期開始学习MySQL,主要是通过书籍,和看燕十八老师的视频,然后通过博客记录自己的学习过程. 登入数据库 zhiniaobu@telunsu-K55VD:~$ mysql -uroot -p Ent ...

  6. 我和小美的撸码日记(3)之中的一个句话搞定MVC表单页数据绑定与提交

    另外献上在<线体验Demo地址>希望大家也能从中得到一些启示. 地址:http://121.40.148.178:8080/ . username:guest,password:12345 ...

  7. 有向无环图(DAG)的最小路径覆盖

    DAG的最小路径覆盖 定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点. 最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖. 最小不相交路径覆盖:每一条路径经过的顶点各不相同.如 ...

  8. Android 性能优化 五 性能分析工具dumpsys的使用

    Android提供的dumpsys工具能够用于查看感兴趣的系统服务信息与状态,手机连接电脑后能够直接命令行运行adb shell dumpsys 查看全部支持的Service可是这样输出的太多,能够通 ...

  9. MFC控件(15):Tooltip

    在各种软件产品中我们经常碰到把鼠标放到一个控件上时会弹出关于该控件的一些提示信息.这就是tooltip. 在MFC中使用该功能可以使用类CToolTipCtrl.假如要让鼠标放到按钮IDC_BTN上时 ...

  10. 隐马尔科夫模型(HMM)及事实上现

    马尔科夫模型 马尔科夫模型是单重随机过程,是一个2元组:(S,A). 当中S是状态集合,A是状态转移矩阵. 仅仅用状态转移来描写叙述随机过程. 马尔科夫模型的2个如果 有限历史性如果:t+l时刻系统状 ...