歡迎加入QQ討論群258996829
月之殘骸 頭像
蘋果2袋
2
月之殘骸

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

發(fā)布時間:2016-01-18 22:48  回復(fù):0  查看:4409   最后回復(fù):2016-01-18 22:48  

原文 Beginning Alamofire Tutorial

原文作者 Essan Parto

譯者 星夜暮晨(QQ:412027805)


Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程
學習如何使用 Alamofire 來輕松地實現(xiàn) Swift 的網(wǎng)絡(luò)請求處理

AFNetworking是 iOS 和 OS X 上最受歡迎的第三方庫之一。它曾在我們的2012年的讀者評選中榮獲2012年度最佳 iOS 庫稱號。它同樣也在 Github 上面獲得了14000多個 stars 和4000多個 forks,是使用最廣的開源項目之一。

最近,AFNetworking 的作者 Mattt Thompson提交了一個新的類似于 AFNetworking 的網(wǎng)絡(luò) 基礎(chǔ)庫,并且是專門使用最新的 Swift 語言來編寫的,名為:Alamofire

AFNetwork 的前綴 AF 是 Alamofire 的縮寫,因此這個新的庫名稱其實是根據(jù) Swift 的約定來進行命名的。

在本教程的第一部分中,我們將帶領(lǐng)大家使用 Alamofire 來制作一個圖片庫應(yīng)用,其來源是500px.com。在這個過程中,您可以學習到如何使用 Alamofire 中的重要組件,以及了解在應(yīng)用中處理網(wǎng)絡(luò)請求的某些重要的知識點。

本教程的第二部分將基于第一部分所制作的應(yīng)用,并為其增加一些好用的功能。您可以通過這個過程學習到更多高級的 Alamofire 用法。

本教程假定您已熟悉 Swift 語言以及 iOS 開發(fā)。如果不是的話,請參閱我們的其他教程。還有,本教程使用 Xcode 7.1 作為開發(fā)環(huán)境。

提示:

如果您已經(jīng)熟悉了 Alamofire 的基本用法,那么您可以直接跳到本文的第二部分。但是請確保您已擁有消費者密鑰(cunsumer key),然后參照下文在應(yīng)用中替換它。

讓我們開始吧

首先下載本次教程的初始項目。這個項目中提供了在本教程中需要的全部 UI。這有助于您能夠?qū)⒆⒁饬械綄W習 Alamofire 的使用上來,而不是花費大量時間來研究 UI。

在 Xcode 中打開這個項目,并定位到Main.storyboard文件:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程
Main.storyboard

我們應(yīng)用的主屏幕使用UITabBarController這個常用的 UI 樣式。我們的標簽控制器中包含有兩個標簽,每個標簽都有它們自己的UINavigationController頁面。第一個標簽讓用戶瀏覽熱門圖片。第二個標簽讓用戶瀏覽他們已保存的文件。兩個標簽都使用UICollectionViewController來向用戶展示圖片。故事板中同樣也包含了兩個獨立的視圖控制器,在接下來的教程中我們將會用到它們。

生成并運行該應(yīng)用,您首先會看到一個不停在加載的加載控件:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程
初始項目的運行效果

這看起來一點也不高端大氣上檔次……可以說沒什么可看的。但是很快我們將會借助 Alamofire 來讓他逼格高起來。

提示:

如果您很熟悉 AFNetworking 的使用,那么您可能會期待下一節(jié)我們談?wù)?CocoaPods相關(guān)用法。借助CocoaPods 0.36及以上版本,您便可以在iOS 8.0以上環(huán)境中使用Swift的內(nèi)置框架了。

在本教程當中,我們將會使用另外一個不需要CocoaPods的方法來導入Alamofire。當然,如果您對CocoaPods很熟悉,那么也可以盡情地使用它來導入,這兩者基本沒有任何區(qū)別。

要獲取最新版本的 Alamofire,請前往https://github.com/Alamofire/Alamofire然后單擊網(wǎng)頁右邊的Download ZIP按鈕。接著在 Finder 中打開起始項目文件夾,然后將Alamofire-master 文件夾拖入到您的主項目文件夾中。

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

打開Alamofire-master文件夾(現(xiàn)在它位于您的項目文件夾中),然后將Alamofire.xcodeprij文件(注意是藍色圖標!不是白色圖標?。┲苯油线M Xcode 中的 Photomania 項目下面,如下圖所示:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

接下來,單擊Photomania項目,進入 General 選項卡。向下滾動到Embedded Binaries項,然后單擊其下方的 + 號。選擇Alamofire.framework,最后點擊Add完成添加。

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

生成并運行您的項目以確保沒有任何錯誤出現(xiàn),然后就可以進入到下一節(jié)內(nèi)容了。

使用 Alamofire 來檢索數(shù)據(jù)

您可能會覺得,為什么我們要使用 Alamofire 呢?明明蘋果已經(jīng)提供了NSURLSession類以及其相關(guān)類,以便讓我們通過 HTTP 來下載相應(yīng)內(nèi)容。為什么我們還要傷精費神地去使用第三方庫呢?

簡單來說,Alamofire 其實是基于NSURLSession的,但是它可以免去您寫樣板(boilerplate)代碼的麻煩,并且可以讓網(wǎng)絡(luò)模塊的代碼更為簡單易用。您可以通過一些非常簡單的操作來訪問 Internet 上的數(shù)據(jù),并且寫出來的代碼也會更加清晰明了、簡單易讀。

要使用 Alamofire 的話,首先需要導入它。請打開PhotoBrowserCollectionViewController.swift文件,然后在文件頂部添加如下代碼:

import Alamofire

您需要在每個使用了 Alamofire 類以及函數(shù)的文件中添加這條import語句。

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

Alamofire.request(.GET, "https://api.500px.com/v1/photos").responseJSON() {
    response in
    guard let JSON = response.result.value else { return }
    print("JSON: \(JSON)")
}
過會兒我會對其做出詳細解釋,但是首先您需要生成并運行該應(yīng)用,這個時候您會在控制臺中看到如下信息:
JSON: {
    error = "Consumer key missing.";
    status = 401;
}

您可能不明白它說了什么鬼,不過實際上您已經(jīng)成功地使用 Alamofire 來實現(xiàn)網(wǎng)絡(luò)請求了!您向 Internet 上的資源發(fā)出了一個請求,然后返回了一個JSON 數(shù)據(jù)。

下面來解釋一下那些代碼到底做了些什么:

  • Alamofire.request(_:_)接收兩個參數(shù):method和URLString。其中,method 通常是.GET、.POST;URLString通常是您想要訪問的內(nèi)容的 URL。其將返回一個Alamofire.Request對象。
  • 通常情況下,您只需將請求對象鏈接到響應(yīng)方法上。例如,在上面的代碼中,請求對象簡單地調(diào)用了responseJSON()方法。當網(wǎng)絡(luò)請求完畢后,responseJSON()方法會調(diào)用我們所提供的閉包。在我們的示例中,我們只是簡單的將經(jīng)過解析的 JSON 輸出到控制臺中。
  • 調(diào)用responseJSON方法意味著您期望獲得一個 JSON 數(shù)據(jù)。在我們的示例中,Alamofire 試圖解析響應(yīng)數(shù)據(jù)并返回一個 JSON 對象?;蛘?,您可以使用responsePropertyList來請求獲得一個屬性列表,也可以使用responseString來請求獲得一個初始字符串。在本教程后面,您將了解更多關(guān)于響應(yīng)序列化方法的使用方式。

您可以從控制臺中看到輸出的響應(yīng)數(shù)據(jù),服務(wù)器報告您需要一個名為consumer key的東西。在我們繼續(xù)使用 Alamofire 之前,我們需要從 500px 網(wǎng)站的 API 中獲取一個密鑰。

獲取消費者密鑰

前往https://500px.com/signup,然后使用您的郵箱免費注冊,或者使用您的 Facebook 、Twitter 或者 Google 帳號登錄。

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

您會看到如下所示的對話框:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

紅色大箭頭指向的那些文本框里面的內(nèi)容都是必填的。使用Alamofire Tutorial作為Application Name,然后使用iOS App作為Description。目前您的應(yīng)用還沒有Application URL,但是您可以隨意輸一個有效的網(wǎng)址來完成應(yīng)用注冊,您可以使用raywenderlich.com^_^。

最后,在Developer’s Email中輸入您的郵箱地址,然后單擊復(fù)選框來接受使用協(xié)議。

接著,單擊 Register按鈕,您會看到一個如下所示的框:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

單擊See application details鏈接,然后它會彈出詳細信息,這時候您就可以定義您的消費者密鑰了,如下所示:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

從該頁面中復(fù)制出您的消費者密鑰,然后返回 Xcode,然后用如下代碼替換掉目前為止您唯一添加的代碼:

Alamofire.request(.GET, "https://api.500px.com/v1/photos", parameters: ["consumer_key": "消費者密鑰"]).responseJSON() {
        response in
        guard let JSON = response.result.value else { return }
        print("JSON: \(JSON)")
    }

請確保您已經(jīng)用復(fù)制的消費者密鑰來替換掉消費者密鑰。

生成并運行您的應(yīng)用,這時您會在控制臺中看見海量的輸出:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

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

JSON 數(shù)據(jù)中包含了一些圖片集的屬性、一些頁面信息,以及一個圖片數(shù)組。這里是我得到的搜索結(jié)果(您的可能會略有不同):

{
  "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 ...",
    .
    .
    .
    }
  ]
}

現(xiàn)在我們已經(jīng)擁有了 JSON 數(shù)據(jù),接下來我們就可以好好地利用它了。

使用如下代碼替換掉viewDidLoad()中的 print("JSON: \(JSON)")方法:

guard let photoJsons = JSON.valueForKey("photos") as? [NSDictionary] else { return }

photoJsons.forEach {
    guard let nsfw = $0["nsfw"] as? Bool,
          let id = $0["id"] as? Int,
          let url = $0["image_url"] as? String
          where nsfw == false else { return }
    self.photos.insert(PhotoInfo(id: id, url: url))
}

self.collectionView!.reloadData()

上述代碼將 JSON 數(shù)據(jù)轉(zhuǎn)變?yōu)榱烁子诠芾淼腜hotoInfo對象集合。這些對象只是簡單存儲了圖片 ID 和 URL 屬性的存儲桶(bucket)。您同樣可以發(fā)現(xiàn)代碼過濾掉了一些……呃……您不希望出現(xiàn)的一些圖片。

上述代碼同樣也重新加載了集合視圖。初始項目的示例代碼基于我們剛剛填充的photos,來創(chuàng)建集合視圖的單元。

生成并運行您的應(yīng)用,這時加載控件加載一會兒便消失。如果您仔細觀察的話,您會發(fā)現(xiàn)一堆灰黑色的方形單元格:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

離我們的目標越來越接近了,加油!

我們?nèi)匀贿x擇PhotoBrowserCollectionViewController.swift文件,在collectionView(_: cellForItemAtIndexPath:)方法中的return cell前加上如下的代碼:

let photoInfo = self.photos[self.photos.startIndex.advancedBy(indexPath.item)]

Alamofire.request(.GET, photoInfo.url).response() {
    _, _, data, _ in
    guard let data = data else { return }
    let image = UIImage(data: data)
    cell.imageView.image = image
}

上述的代碼為photos集合中的對象創(chuàng)建了另外的 Alamofire 請求。由于這是一個圖片請求,因此我們使用的是一個簡單的request方法,其在閉包中返回NSData響應(yīng)。接下來我們直接把數(shù)據(jù)放入到一個UIImage的實例中,然后反過來將實例放入早已存在于示例項目中的imageView 當中。

再一次生成并運行您的應(yīng)用,這時應(yīng)當出現(xiàn)一個圖片集合,與下圖相似:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

對于 Alamofire 的工作效果想必您現(xiàn)在已經(jīng)心中有數(shù),但是您不會想在每次從服務(wù)器請求數(shù)據(jù)的時候,要不停的復(fù)制、粘貼 API 地址,以及添加消費者密鑰。除了這一點非常讓人不爽外,如果 API 地址發(fā)生了改變,那么您可能不得不再次創(chuàng)建一個新的消費者密鑰。

幸運的是,Alamofire對于這個問題有著良好的解決方案。

創(chuàng)建請求路由

打開Five100px.swift,然后找到struct Five100px,其中定義了enum ImageSize。這是一個簡單的基于 500px.com 的 API 文件的結(jié)構(gòu)體。

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

import Alamofire
現(xiàn)在,在struct Five100px 中的enum ImageSize 代碼段上方添加下述代碼:
enum Router: URLRequestConvertible {
    static let baseURLString = "https://api.500px.com/v1"
    static let consumerKey = "消費者密鑰"

    case PopularPhotos(Int)
    case PhotoInfo(Int, ImageSize)
    case Comments(Int, Int)

    var URLRequest: NSMutableURLRequest {
        let result: (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):
                let params = ["consumer_key": Router.consumerKey, "image_size": "\(imageSize.rawValue)"]
                return ("/photos/\(photoID)", params)
            case .Comments(let photoID, let commentsPage):
                let 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(result.path))
        let encoding = Alamofire.ParameterEncoding.URL

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

這就是我們所創(chuàng)建的路由,它為我們的 API 調(diào)用方法創(chuàng)建合適的URLString實例。它是一個簡單的遵守URLRequestConertible協(xié)議的enum類型,這個協(xié)議是在 Alamofire 當中定義的。當有枚舉類型采用該協(xié)議的時候,該類型就必須含有一個名為URLRequest的NSMutableURLRequest類型變量。

這個路由含有兩個靜態(tài)常量:API 的baseURLString以及consumerKey。(最后一次聲明,請將消費者密鑰替換為您自己的消費者密鑰)現(xiàn)在,這個路由可以在必要的時候向最終的URLString中添加消費者密鑰。

您的應(yīng)用擁有三個 API 終端(endpoint):一個用來取出熱門照片列表,一個用來取出某個特定照片的具體信息,一個用來取出某個照片的評論。路由將會借助三個相應(yīng)的case聲明來處理這三個終端,每個終端都會接收一到兩個參數(shù)。

我們已經(jīng)定義了var URLRequest: NSURLMutableRequest這個計算(computed)屬性。這意味著每次我們使用enum的時候,它都會構(gòu)造出基于特定case和其參數(shù)的最終 URL。

這里有一個示例代碼片段,說明了上述的邏輯關(guān)系:

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 的終結(jié)點來尋找一個 ID 為10000的大尺寸圖片。注釋行將 URL 的結(jié)構(gòu)進行了拆分。在這個示例中,URL 由三個部分組成:baseURLString、path(?前面的那一部分)以及[String: AnyObject]字典,其中包含有傳遞給 API 終結(jié)點的參數(shù)。

對于path來說,返回元組的第一個元素可以用以下的字符串形式返回:

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

和響應(yīng)解析類似,請求參數(shù)可以被編碼為 JSON、屬性列表或者是字符串。通常情況下使用簡單的字符串參數(shù),和上面我們所做的類似。

如果您打算在您自己的項目中使用路由,您必須對它的運行機制相當熟悉。為此,請嘗試搞清楚要如何構(gòu)造出以下的 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: NSMutableURLRequest {
  let results: (path: String, parameters: [String: AnyObject]) = {
    switch self {
    case .UserLists(let userID):
      let params = ["v": "20131016", "group": "created"]
      return ("/users/\(userID)/lists", params)
    }
  }()
.
.
.
這里您需要為枚舉添加其他的 case,比如說用戶列表,它們都設(shè)置有合適的參數(shù)和路徑。

加載更多圖片

好的,現(xiàn)在應(yīng)用目前顯示的照片只有一個頁面,但是我們想要瀏覽更多照片以找到我們心儀的內(nèi)容。多多益善,對吧?

打開PhotoBrowserCollectionViewController.swift,然后在 let refreshControl = UIRefreshControl()語句下方添加如下代碼:

var populatingPhotos = false
var currentPage = 1

這里我們定義了兩個變量,來記錄當前是否在更新照片,以及當前我們正在瀏覽的是哪一個照片頁面。

接下來,用以下代碼替換當前viewDidLoad()的聲明:

override func viewDidLoad() {
  super.viewDidLoad()

  setupView()

  populatePhotos()
}

這里我們用populatePhotos()函數(shù)來替換了先前的 Alamofire 請求。之后我們就要實現(xiàn)populatePhotos()函數(shù)的聲明。

同樣的,在handleRefresh()上方添加兩個函數(shù),如下所述:

// 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() {
        response in
        func failed() { self.populatingPhotos = false }
        guard let JSON = response.result.value else { failed(); return }
        if response.result.error != nil { failed(); return }

        // 4
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
            // 5
            guard let photoJsons = JSON.valueForKey("photos") as? [NSDictionary] else { return }

            // 6
            let lastItem = self.photos.count

            // 7
            photoJsons.forEach {
                guard let nsfw = $0["nsfw"] as? Bool,
                    let id = $0["id"] as? Int,
                    let url = $0["image_url"] as? String
                    where nsfw == false else { return }
                // 8
                self.photos.insert(PhotoInfo(id: id, url: url))
            }

            // 9
            let indexPaths = (lastItem..<self.photos.count).map { NSIndexPath(forItem: $0, inSection: 0) }

            // 10
            dispatch_async(dispatch_get_main_queue()) {
                self.collectionView!.insertItemsAtIndexPaths(indexPaths)
            }

            self.currentPage++

        }
    }
    self.populatingPhotos = false
}

啊……好長一段代碼,對吧?下面是對每個注釋部分的詳細解釋:

  1. 一旦您滾動超過了 80% 的頁面,那么scrollViewDidScroll()方法將會加載更多的圖片。
  2. populatePhotos()方法在currentPage當中加載圖片,并且使用populatingPhotos作為標記,以防止還在加載當前界面時加載下一個頁面。
  3. 這里我們首次使用了我們創(chuàng)建的路由。只需將頁數(shù)傳遞進去,它將為該頁面構(gòu)造 URL 字符串。500px.com 網(wǎng)站在每次 API 調(diào)用后返回大約50張圖片,因此您需要為下一批照片的顯示再次調(diào)用路由。
  4. 要注意,.responseJSON()后面的代碼塊:completion handler(完成處理方法)必須在主線程運行。如果您正在執(zhí)行其他的長期運行操作,比如說調(diào)用 API,那么您必須使用 GCD 來將您的代碼調(diào)度到另一個隊列運行。在本示例中,我們使用DISPATCH_QUEUE_PRIORITY_HIGH來運行這個操作。
  5. 您可能會關(guān)心 JSON 數(shù)據(jù)中的photos關(guān)鍵字,其位于數(shù)組中的字典中。每個字典都包含有一張圖片的信息。
  6. 接下來我們會在添加新的數(shù)據(jù)前存儲圖片的當前數(shù)量,使用它來更新collectionView
  7. forEach函數(shù)將遍歷獲取到的photoJsons字典數(shù)組,篩除掉nsfw(Not Safe For Work)圖片,然后將PhotoInfo對象插入到photos集合當中。這個類是在Five100px.swift當中定義的。如果您查看這個類的源碼,那么就可以看到它重寫了isEqual和hash這兩個方法。這兩個方法都是用一個整型的 id 屬性,因此排序和唯一化(uniquing)PhotoInfo對象仍會是一個比較快的操作
  8. 如果有人在我們滾動前向 500px.com 網(wǎng)站上傳了新的圖片,那么您所獲得的新的一批照片將可能會包含一部分已下載的圖片。這就是為什么我們定義var photos = Set<PhotoInfo>()為一個集合。由于集合內(nèi)的項目必須唯一,因此重復(fù)的圖片不會再次出現(xiàn)
  9. 這里我們創(chuàng)建了一個NSIndexPath對象的數(shù)組,并將其插入到collectionView
  10. 在集合視圖中插入項目,請在主隊列中完成該操作,因為所有的 UIKit 操作都必須運行在主隊列中

生成并運行您的應(yīng)用,然后緩慢向下滑動圖片。您可以看到新的圖片將持續(xù)加載:

Alamofire網(wǎng)絡(luò)庫基礎(chǔ)教程

不斷加快滑動的速度,注意到問題沒有?對的,滾動操作不是很穩(wěn)定,有些許遲鈍的感覺。這并不是我們想要提供給用戶的體驗,但是我們在下一節(jié)中就可以修正這個問題了。

創(chuàng)建自定義響應(yīng)序列化方法(Serializer)

您已經(jīng)看到,我們在 Alamofire 中使用所提供的 JSON、字符串,以及屬性列表序列化方法是一件非常簡單的事情。但是有些時候,您可能會想要創(chuàng)建自己的自定義相應(yīng)序列化。例如,您可以寫一個響應(yīng)序列化方法來直接接收UIIMage,而不是將UIImage轉(zhuǎn)化為NSData來接收。

在本節(jié)中,您將學習如何創(chuàng)建自定義響應(yīng)序列化方法。

打開Five100px.swift,然后在靠近文件頂部的地方,也就是import Alamofire語句下面添加如下代碼:

extension Alamofire.Request {
    class func imageResponseSerializer() -> ResponseSerializer<UIImage?, NSError> {
        return ResponseSerializer { request, response, data, error in
            guard error == nil else { return .Failure(error!) }

            guard let validData = data else {
                let failureReason = "數(shù)據(jù)無法被序列化,因為接收到的數(shù)據(jù)為空"
                let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
                return .Failure(error)
            }

            let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale)
            return .Success(image)
        }
    }

    public func responseImage(completionHandler: Response<UIImage?, NSError> -> Void) -> Self {
        return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: completionHandler)
    }
}
要創(chuàng)建一個新的響應(yīng)序列化方法,我們首先應(yīng)當需要一個類方法,其返回ResponseSerializer 結(jié)構(gòu)體(比如說上面所寫的imageResponseSerializer() )。這個結(jié)構(gòu)體是 Alamofire 中處理序列化的主體,結(jié)構(gòu)如下所示:
public struct ResponseSerializer<SerializedObject, ErrorObject: ErrorType>: ResponseSerializerType { ... }

類方法(例如imageResponseSerializer())接收底層的NSURLSession請求以及和響應(yīng)對象一起的基本NSData數(shù)據(jù)實現(xiàn)方法(從服務(wù)器傳來的),來作為參數(shù)。該方法接下來使用這些對象來序列化,并將其輸入到一個有意義的數(shù)據(jù)結(jié)構(gòu)中,然后將其從方法中返回,它同樣也會返回在這個過程中發(fā)生的錯誤。在我們的示例中,我們使用UIImage來將數(shù)據(jù)轉(zhuǎn)化為圖片對象。

通常情況下,當您創(chuàng)建了一個響應(yīng)序列化方法后,您可能還會向創(chuàng)建一個新的響應(yīng)處理方法來對其進行處理,并讓其更加易用。我們使用.responseImage()方法來完成這項任務(wù)。這個方法的操作很簡單:它使用completionHandler,一個以閉包形式的代碼塊。一旦我們從服務(wù)器中序列化了數(shù)據(jù),那么這個代碼塊將會運行。我們所需要做的就是在響應(yīng)處理方法中調(diào)用 Alamofire 自己的通用.response()響應(yīng)處理方法。

讓我們開始讓它工作起來。打開PhotoBrowserCollectionViewController.swift,然后在PhotoBrowserCollectionViewCell中的imageView屬性下面,添加如下一個屬性:

var request: Alamofire.Request?

這個屬性會為這個單元存儲 Alamofire 的請求來加載圖片

現(xiàn)在將collectionView(_: cellForItemAtIndexPath:)的內(nèi)容替換為下面所示的代碼:

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

let imageURL = self.photos[self.photos.startIndex.advancedBy(indexPath.item)].url
cell.imageView.image = nil
cell.request?.cancel()

cell.request = Alamofire.request(.GET, imageURL).responseImage {
    response in
    guard let image = response.result.value where response.result.error == nil else { return }
    cell.imageView.image = image
}

return cell

生成并運行您的應(yīng)用,再次滾動圖片,您會發(fā)現(xiàn)滾動變得流暢了。

為什么會流暢呢?

那么我們到底做了些什么來讓滾動變得流暢了呢?其關(guān)鍵就是collectionView(_: cellForItemAtIndexPath:)中的代碼。但是在我們解釋這段代碼之前,我們需要向您解釋網(wǎng)絡(luò)調(diào)用的異步性。

Alamofire 的網(wǎng)絡(luò)調(diào)用是異步請求方式。這意味著提交網(wǎng)絡(luò)請求不會阻止剩余代碼的執(zhí)行。網(wǎng)絡(luò)請求可能會執(zhí)行很長時間才能得到返回結(jié)果,但是您不會希望在等待圖片下載的時候 UI 被凍結(jié)。

也就是說,實現(xiàn)異步請求是一個極大的挑戰(zhàn)。如果在發(fā)出請求之后到從服務(wù)器接收到響應(yīng)的這段時間中,UI 發(fā)生了改變的話怎么辦?

例如,UICollectionView擁有內(nèi)部的單元出列機制。創(chuàng)建新的單元對系統(tǒng)來說開銷很大,因此集合視圖將重用不在屏幕上顯示的現(xiàn)有單元,而不是不停創(chuàng)建新的單元。

這意味著同一個單元對象,會被不停地重復(fù)使用。因此在發(fā)出 Alamofire 請求之后到接收到圖片信息響應(yīng)之前,用戶將單元滾動出屏幕并且刪除圖片的操作將成為可能。單元可能會出列,并且準備顯示另一個圖片。

在上述的代碼中,我們完成了兩件事來解決這個問題。第一件事是,當一個單元出列后,我們通過設(shè)值為nil的方法來清除圖片。這個操作確保我們不會顯示原先的圖片;第二件事是,我們的請求完成處理方法將檢查單元的 URL 是否和請求的 URL 相等。如果不相等的話,顯然單元已經(jīng)擁有了另外的圖片,那么完成處理方法將不會浪費其生命周期來為單元設(shè)置錯誤的圖片。

接下來該何去何從?

您可以在這里下載本教程第一部分的最終版本項目。

提示:

如果您打算直接使用上面的的最終版本,那么千萬不要忘記在前面的教程中所說的,用您的消費者密鑰酌情替換Five100px.swift中的相應(yīng)內(nèi)容。

本教程介紹了相當多的內(nèi)容,現(xiàn)在您可以好好的休息一下了!現(xiàn)在,多虧了 Alamofire,您的應(yīng)用擁有了基本的照片瀏覽功能。

在我們的學習過程中,您已經(jīng)學會了如何使用 Alamofire 發(fā)送 GET 請求、傳遞參數(shù)、創(chuàng)建請求路由,甚至學會了創(chuàng)建您自己的響應(yīng)序列化方法。

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

  • 照片查看器
  • 查看評論以及其他信息的能力
  • 下載照片的選項,附帶有一個圓列進度條
  • 下拉刷新操作

我們希望您能夠喜歡我們這部分的教程,并且能夠加入我們的第二部分的教程。如果您對 Alamofire 有任何看法或建議,快來加入我們的討論吧!


您還未登錄,請先登錄

熱門帖子

最新帖子

?