你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> Alamofire網絡庫進階教程

Alamofire網絡庫進階教程

編輯:IOS開發基礎

18.jpg

本章節由CocoaChina翻譯組成員星夜暮晨(博客)翻譯自raywenderlich:Intermediate Alamofire Tutorial,敬請勘誤。

歡迎回到我們的 Alamofire 網絡庫使用教程,本文是此教程的第二部分,同時也是最後一個部分。

教程的第一部分中,我們學習了 Alamofire 的一些基本用法,比如說發送 GET 請求、傳遞參數、創建請求路由以及創建自定義響應序列化方法。在學習的過程中,我們也生成了一個很贊的名為 Photomania 的圖片庫應用。在本教程的第二部分,您將會增加以下功能:

  • 照片查看器

  • 查看評論以及其他信息的功能

  • 下載照片功能,附帶有一個圓列進度條

  • 優化網絡訪問以及圖片緩存

  • 下拉刷新操作

讓我們開始吧

您可以使用您在教程第一部分所完成的項目來開始本章教程。但是如果您跳過了第一部分的教程或者對自己的項目沒有信心的話,那麼您也可以使用我們提供的標准項目。

提示:

如果您跳過了第一部分的教程,那麼請不要忘記您首先應當從 500px.com 網站上獲取消費者密鑰,然後在Five100px.swift中用其替換必要的部分。關於如何獲取該密鑰,以及在何處使用它,都在本教程的第一部分:Alamofire 網絡庫基礎教程中有詳細說明。

生成並運行起始項目,以確定我們應用運行正常。圖片預覽功能能夠正常工作,但是單擊圖片並不會將其以全屏打開。這就是我們所要解決的問題!

創建圖片查看器

說句老實話,范型可以說是包括 Swift 在內的高級編程語言中最強大的特性之一。一般情況下,在我們這個項目中最好使用范型這個功能。

打開Five100px.swift,然後在文件頂部,即import Alamofire語句下方添加以下代碼:

@objc public protocol ResponseObjectSerializable {
  init(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Alamofire.Request {
  public func responseObject(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
    let serializer: Serializer = { (request, response, data) in
      let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
      let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
      if response != nil && JSON != nil {
        return (T(response: response!, representation: JSON!), nil)
      } else {
        return (nil, serializationError)
      }
    }
    return response(serializer: serializer, completionHandler: { (request, response, object, error) in
      completionHandler(request, response, object as? T, error)
    })
  }
}

在上述的代碼中,我們再一次給 Alamofire 創建了一個擴展,添加了新的響應序列化方法。這次,我們添加了.responseObject()函數。作為一個通用函數,它能夠序列化所有符合ResponseObjectSerializable協議的數據對象。

這意味著,如果我們定義一個含有init(response:representation:)初始化方法的新類,那麼 Alamofire 就能夠自行從服務器返回該類型的對象。這時候,我們已經將序列化邏輯封裝進了自定義類的內部。哈哈,是不是一個很贊的面向對象設計?

44.jpg

圖片查看器使用的是PhotoInfo類,這個類遵守了ResponseObjectSerializable協議,並實現了所需的方法。不過您仍需要讓這個類正式遵守ResponseObjectSerializable協議。

打開Five100px.swift,並且修改PhotoInfo類的聲明來讓其明確遵守ResponseObjectSerializable協議,如下所示:

class PhotoInfo: NSObject, ResponseObjectSerializable {

提示:

雖然毋需詳細了解representation參數在PhotoInfo對象中是如何序列化的,但是感興趣的讀者可以去浏覽required init(response:representation:)方法來了解其工作原理。

打開PhotoViewerViewController.swift,注意不是PhotoBrowserCollectionViewController.swift,然後在文件頂部加入一個必要的導入聲明:

import Alamofire

接著,在viewDidLoad()方法內的底部加入以下代碼:

loadPhoto()

您會得到一個找不到loadPhoto()的錯誤,但是不必擔心,我們接下來就要實現這個函數。

仍然是在同一個文件當中,在setupView()方法前加入以下代碼:

func loadPhoto() {
  Alamofire.request(Five100px.Router.PhotoInfo(self.photoID, .Large)).validate().responseObject() {
    (_, _, photoInfo: PhotoInfo?, error) in
    if error == nil {
      self.photoInfo = photoInfo
      dispatch_async(dispatch_get_main_queue()) {
        self.addButtomBar()
        self.title = photoInfo!.name
      }
      Alamofire.request(.GET, photoInfo!.url).validate().responseImage() {
        (_, _, image, error) in
        if error == nil && image != nil {
          self.imageView.image = image
          self.imageView.frame = self.centerFrameFromImage(image)
          self.spinner.stopAnimating()
          self.centerScrollViewContents()
        }
      }
    }
  }
}

這時,我們在其他 Alamofire 請求的完成處理方法中發出了 Alamofire 請求。第一個請求接收到了一個 JSON 響應數據,然後它使用我們新建的通用響應序列化方法,在JSON 數據之外創建了一個PhotoInfo實例。

(_, _, photoInfo: PhotoInfo?, error) in是響應序列化方法的參數。剛開始的兩個下劃線"_"意味著我們忽略了前兩個參數,我們無需明確地將它們明確命名為request和response。

第三個參數明確被聲明為了我們的PhotoInfo實例,因此通用序列化方法將自行初始化,並返回該類型的一個對象,其包含有圖片 URL。第二個 Alamofire 請求使用您先前創建過的圖片序列化方法,將NSData轉化為UIImage,以便之後我們在圖片視圖中顯示它。

注意:

我們不在這裡使用路由,因為我們已經有了圖片的絕對 URL 地址,我們無需自行構造 URL。

在請求響應對象之前調用的.validate()函數是另一個易用的 Alamofire 特性。將其與請求和響應鏈接,以確認響應的狀態碼在默認可接受的范圍(200到299)內。如果認證失敗,響應處理方法將出現一個相關錯誤,您可以在完成處理方法中處理這個錯誤。

即使沒有發生錯誤,完成處理方法仍然還是會被調用。第四個參數error是NSError的一個實例,它的值讓我們能夠在自定義方法中響應這個錯誤。

生成並運行您的應用,單擊其中一副圖片後您應當看到它將覆蓋全屏幕,如下所示:

QQ截圖20141203100624.jpg

好的!圖片查看器正常工作了,雙擊該圖片可以放大圖片。

當類型安全的通用響應序列化方法初始化PhotoInfo的時候,您不僅僅只是設置了id和url屬性,還有一些屬性您並沒有看見。

單擊應用左下方的Menu按鈕,然後您就可以看到該照片的詳細信息:

QQ截圖20141203100729.jpg

單擊屏幕上的任意位置就可以關閉照片詳細信息。

如果您對 500px.com很熟悉的話,您就知道用戶往往會在網站上給極佳的照片留下很多評論信息。現在既然我們的圖片查看器正常工作了,那麼現在我們就要開始搭建評論查看器了。

為顯示注釋創建集合序列化方法

對於有評論的圖片來說,圖片查看器會顯示一個包含評論數的評論按鈕。單擊這個評論按鈕會彈出一個評論列表。

打開Five100px.swift,然後在import Alamofire聲明下添加以下代碼:

@objc public protocol ResponseCollectionSerializable {
  class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
  public func responseCollection(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
    let serializer: Serializer = { (request, response, data) in
      let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
      let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
      if response != nil && JSON != nil {
        return (T.collection(response: response!, representation: JSON!), nil)
      } else {
        return (nil, serializationError)
      }
    }
    return response(serializer: serializer, completionHandler: { (request, response, object, error) in
      completionHandler(request, response, object as? [T], error)
    })
  }
}

這段代碼看起來很眼熟,它和我們之前創建的通用響應序列化方法相似。

唯一的不同點是,這個協議定義了返回集合的一個類方法(而不是單個元素)。完成處理方法將集合作為其第三個參數,即[T],接著調用類型上的collection而不是調用初始化方法。

仍然是在同一個文件當中,將整個Comment類替換為以下形式:

final class Comment: ResponseCollectionSerializable {
  class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Comment] {
    var comments = [Comment]()
    for comment in representation.valueForKeyPath("comments") as [NSDictionary] {
      comments.append(Comment(JSON: comment))
    }
    return comments
  }
  let userFullname: String
  let userPictureURL: String
  let commentBody: String
  init(JSON: AnyObject) {
    userFullname = JSON.valueForKeyPath("user.fullname") as String
    userPictureURL = JSON.valueForKeyPath("user.userpic_url") as String
    commentBody = JSON.valueForKeyPath("body") as String
  }
}

這段代碼讓Comment遵守ResponseCollectionSerializable協議,因此它將和上面的響應序列化方法協同工作。

現在您需要做的就是使用它。打開PhotoCommentsViewController.swift然後在文件頂部加入以下必要的導入聲明:

import Alamofire

現在在viewDidLoad()方法中的底部加入以下代碼:

Alamofire.request(Five100px.Router.Comments(photoID, 1)).validate().responseCollection() {
  (_, _, comments: [Comment]?, error) in
  if error == nil {
    self.comments = comments
    self.tableView.reloadData()
  }
}

這段代碼使用了您新建的響應序列化方法來解序列化位於Comment集合中響應的NSData,然後將它們保存在屬性當中,最後在表視圖中重新加載它們。

接下來,向tableView(_:cellForRowAtIndexPath)中添加以下代碼(在return cell語句上面):

cell.userFullnameLabel.text = comments![indexPath.row].userFullname
cell.commentLabel.text = comments![indexPath.row].commentBody
cell.userImageView.image = nil
let imageURL = comments![indexPath.row].userPictureURL
Alamofire.request(.GET, imageURL).validate().responseImage() {
  (request, _, image, error) in
  if error == nil {
    if request.URLString.isEqual(imageURL) {
      cell.userImageView.image = image
    }
  }
}

這段代碼在表視圖單元格中顯示了評論信息,同樣它還接連提交了第二個 Alamofire 請求來加載圖片(這和我們在教程第一部分所做的相類似)。

生成並運行您的應用,找到一個有評論的圖片。您可以通過評論圖標上的數字來了解該圖片的評論數目。單擊評論按鈕,然後該圖片的評論界面就會顯示出來,如下所示:

QQ截圖20141203100911.jpg

現在,您可能已經發現了幾張您想下載的圖片(也有可能是幾百張哈),下一節我們將帶領大家如何實現下載功能。

顯示下載進度

圖片查看器的底欄中間有一個動作按鈕。它顯示出一個UIActionSheet控件來讓您選擇是否下載照片,但是現在它還沒有任何功能。到目前為止,我們所做的僅僅只是從 500px.com 加載到內存中。那麼我們要如何下載並保存文件呢?

打開PhotoViewerViewController.swift,將空函數downloadPhoto()用以下代碼替換:

func downloadPhoto() {
  // 1
  Alamofire.request(Five100px.Router.PhotoInfo(photoInfo!.id, .XLarge)).validate().responseJSON() {
    (_, _, JSON, error) in
    if error == nil {
      let jsonDictionary = (JSON as NSDictionary)
      let imageURL = jsonDictionary.valueForKeyPath("photo.image_url") as String
      // 2
      let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
      // 3
      Alamofire.download(.GET, imageURL, destination)
    }
  }
}

我們來依次解釋一下這些代碼的作用:

我們首先請求一個新的PhotoInfo,此時請求的是XLarge大小。

獲取要保存文件的默認存儲地址,我們將會將其存放在您應用的 Documents 目錄的一個子目錄中。該子目錄的名字將和服務器建議的名字相同。destination是一個變相的閉包——雖然只是短短的一瞬間。

Alamofire.download(_:_:_)方法和Alamofire.request(_:_)方法有很大的不同。Alamofire.download(_:_:_)方法不需要響應處理方法,也不需要響應序列化方法來對數據進行處理。因為它已經知道如何去處理這些數據了,就是把它們保存在硬盤上!destination閉包將返回要保存圖片的路徑。

生成並運行您的應用,然後找到某個您最喜歡的圖片,單擊動作按鈕,接著單擊Save按鈕。目前您還不會看到任何的回應,但是回到應用主界面來,然後單擊Downloads標簽,這時您就可以看到您下載的照片了。

您或許會問了:“為什麼不直接使用同一個文件路徑來保存呢?”。原因是在我們下載圖片之前我們並不知道圖片的名字。對於 500px.com 來說,服務器始終只會根據圖片的尺寸返回1.jpg、2.jpg、3.jpg、4.jpg或者5.jpg這樣的名字。我們不能夠將相同名字的圖片保存在同樣的文件夾內。

我們使用閉包作為Alamofire.download的第三個參數,而不是傳遞進一個固定的字符串路徑。Alamofire 接著就會在一個合適的時間調用該閉包,然後將temporaryURL和NSHTTPURLResponse傳遞進來作為參數,然後返回一個 URL 的實例,指向硬盤上您有權訪問的路徑。

試著保存一些圖片,然後返回到下載標簽,這時候您會發現,诶?!為啥只有一個文件?鬧哪樣嘛!

這是因為文件名並不是獨一無二的,因此我們需要實現自己的命名邏輯。用以下代碼替換掉downloadPhoto()方法中的注釋 //2 下的語句:

let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
  (temporaryURL, response) in
  if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
    return directoryURL.URLByAppendingPathComponent("\(self.photoInfo!.id).\(response.suggestedFilename)")
  }
  return temporaryURL
}

再次說明,let destination是一個閉包,但是我們這次實現了自己的命名邏輯。我們使用在閉包外捕獲的圖片id,然後將其和服務器建議的名稱相連接,這兩者之間使用“.”來分隔。

生成並運行,然後現在您就可以保存多個圖片了:

QQ截圖20141203101023.jpg

文件保存功能目前工作得很好,但是如果給用戶顯示下載進度的話那豈不是更好?Alamofire 可以很容易地實現顯示下載進度指示器。

用以下代碼替換掉downloadPhoto()方法中的注釋 //3 下的語句:

// 4
let progressIndicatorView = UIProgressView(frame: CGRect(x: 0.0, y: 80.0, width: self.view.bounds.width, height: 10.0))
progressIndicatorView.tintColor = UIColor.blueColor()
self.view.addSubview(progressIndicatorView)
// 5
Alamofire.download(.GET, imageURL, destination).progress {
  (_, totalBytesRead, totalBytesExpectedToRead) in
  dispatch_async(dispatch_get_main_queue()) {
    // 6
    progressIndicatorView.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
    // 7
    if totalBytesRead == totalBytesExpectedToRead {
      progressIndicatorView.removeFromSuperview()
    }
  }
}

我們來依次解釋一下這些代碼的作用:

我們使用標准UIProgressView控件來顯示圖片下載進度。對其進行配置並將其添加到view視圖上。

借助 Alamofire,我們可以將.progress和三個參數進行鏈接。.progress將定期調用一個閉包,而且這三個參數分別是:bytesRead、totalBytesRead以及totalBytesExpectedToRead。

簡單用totalBytesExpectedToRead除以totalBytesRead,我們就可以得到一個0到1之間的數字,這個數字代表這下載進度。如果下載時間不是瞬時的話,那麼這個閉包可能會多次運行。每次運行我們都能夠更新屏幕上的進度條。

一旦下載結束,我們只需從view 視圖上移去進度條。

生成並運行您的應用,找到某個圖片並下載它,這是您就可以看到下載進度條。

QQ截圖20141203101113.jpg

當下載完成時,進度條消失,因此如果您的網絡很快的話那麼您很有可能會看不到這個進度條。

我們注意到,downloadPhoto方法仍然是使用在教程第一部分中用過的.resposneJSON()。我們要確保您能夠理解響應序列化方法是如何工作的。因此請自行創建一個通用的響應序列化方法.responseObject()來替代.resposneJSON()。如果您想要檢查您的解決方案,那麼您可以繼續向下查看我們是如何解決的:

downloadPhoto()方法開頭的幾個語句需要變為以下的形式:

Alamofire.request(Five100px.Router.PhotoInfo(photoInfo!.id, .XLarge)).validate().responseObject() {
  (_, _, photoInfo: PhotoInfo?, error) in
  if error == nil && photoInfo != nil {
    let imageURL = photoInfo!.url
.
.
.

這裡您應當使用自定義的響應序列化方法,而不是手動解析 JSON 數據。這樣可以讓您的代碼更為簡潔。

優化和刷新

好的,現在是時候來實現下拉刷新功能了。(強迫症患者們總是在不停地刷新……刷新……再刷新,對吧?淚一般的事實)

打開PhotoBrowserCollectionViewController.swift,然後用以下代碼替換func handleRefresh():

func handleRefresh() {
  refreshControl.beginRefreshing()
  self.photos.removeAllObjects()
  self.currentPage = 1
  self.collectionView.reloadData()
  refreshControl.endRefreshing()
  populatePhotos()
}

上述的代碼清空您當前的模式(self.photos),然後重置currentPage,最後刷新 UI。

生成並運行您的應用,下拉刷新,這時候您就可以看到新的圖片出現了:

633.jpg

當您快速滑動照片浏覽頁面的時候,您可能會注意到可以將仍然在請求圖片的單元送出屏幕。實際上,圖片請求在其結束前會一直運行,但是下載的照片以及相關數據則會被丟棄。

此外,當您返回到之前的單元,您就必須還得為顯示圖片而發送網絡請求,即使您剛才下載了那幅圖片。我們需要改善這個設計,以防止浪費帶寬。

我們可以利用緩存來保存加載過的圖像,這樣就可以不必再次加載。還有,如果某個單元在網絡請求結束前出列了,我們可以取消其所有的網絡請求。

打開PhotoBrowserCollectionViewController.swift,然後在let refreshControl語句上添加以下代碼:

let imageCache = NSCache()

它創建了一個用於緩存圖片的NSCache對象。

接下來,用以下代碼替換collectionView(_:cellForItemAtIndexPath:):

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowserCellIdentifier, forIndexPath: indexPath) as PhotoBrowserCollectionViewCell
  let imageURL = (photos.objectAtIndex(indexPath.row) as PhotoInfo).url
  // 1
  if cell.request?.request.URLString != imageURL {
    cell.request?.cancel()
  }
  // 2
  if let image = self.imageCache.objectForKey(imageURL) as? UIImage {
    cell.imageView.image = image
  } else {
    // 3
    cell.imageView.image = nil
    // 4
    cell.request = Alamofire.request(.GET, imageURL).validate(contentType: ["image/*"]).responseImage() {
      (request, _, image, error) in
      if error == nil && image != nil {
        // 5
        self.imageCache.setObject(image!, forKey: request.URLString)
        // 6
        if request.URLString == cell.request?.request.URLString {
          cell.imageView.image = image
        }
      } else {
        /*
        If the cell went off-screen before the image was downloaded, we cancel it and
        an NSURLErrorDomain (-999: cancelled) is returned. This is a normal behavior.
        */
      }
    }
  }
  return cell
}

我們來依次解釋一下這些代碼的作用:

1.出隊的單元可能已經有一個連帶的 Alamorire 請求。檢查這個請求是否相關,也就是說,檢查該請求的 URL 是否和要顯示的圖片 URL 相匹配,否則就取消請求。

2.使用可選值綁定來檢查該圖片是否有緩存版本。如果有的話,使用該緩存版本而不是再次下載。

3.如果沒有相應的緩存版本的話,那麼就下載它。然後,出列單元可能已經顯示出了另一幅圖像。這樣子的話,就將其設置為nil,因此當圖片在下載時該單元將為空。

4.從服務器下載圖片,然後驗證返回響應的content-type。如果返回的不是圖片,那麼返回值就是為error。因此您就不會再對這個無效的圖片進行操作了。這裡的關鍵是我們在單元中存儲了 Alamofire 請求對象,當網絡異步調用返回時使用。

5.如果我們沒有接收到錯誤信息,那麼就下載相應的圖片,並在隨後緩存它。

6.檢查單元是否出列以顯示新的圖片。如果沒有的話,則相應地設置單元的圖片。

生成並運行您的應用。您會注意到隨著在照片浏覽器中來回滾動,圖像的加載速度快了許多。我們已經砍掉了不必要的請求,並緩存了已下載的圖片以便重復使用,這些操作讓我們的網絡請求優化了不少。良好的網絡處理和靈活的用戶界面能夠提高用戶體驗的哦!

注意:

某些單元可能會顯示為空,而當它全屏顯示時又不為空。這並不是您的原因,這是因為 500px.com 並沒有這些圖片的縮略圖。

接下來該何去何從?

這裡是本系列教程的完整項目。您會看到本教程沒有涉及到的很多 UI 元素的設計細節。

如果您跟隨我們的教程完成了學習,那麼您現在已經能夠很好的使用最受歡迎的第三方庫—— Alamofire 了。我們學習了可鏈接的請求和響應方法、生成自定義響應序列化方法、創建路由並將 URL 參數進行編碼、下載文件、使用進度條,以及響應驗證。恭喜您獲得了一系列成就!當當當!

Alamofire 同樣能驗證使用不同方案的服務器。同樣,它還可以上傳文件和流數據(並未在本教程中提到)。但是您目前對 Alamofire 的了解,相信您能夠很快了解如何完成這些任務。

Alamofire 目前並沒有實現 AFNetworking 的全部功能。但是如果您使用 Swift 來開始一個新項目,那麼使用 Alamofire 則是最佳選擇,因為它已經涵蓋了最常用的網絡操作。AFNetworking 的最受歡迎的特征之--UIKit 擴展還沒有在 Alamofire 當中實現。但是這兩個庫是可以在同一個項目中並存的。

如果您先前已經使用過 AFNetworking,並且受不了沒有 UIKit 上的類別方法setImageWithURL的話,那麼您可能就還得在您的項目中繼續使用 AFNetworking。例如,您可以用 Alamofire 調用服務器的 API,然後使用 AFNetworking 來異步顯示圖像。 AFNetworking 有一個共享的緩存,這樣您無需手動管理緩存或者取消網絡請求。

您可以用我們的教程項目完成更多更多的事情。您可以通過使用用戶名和密碼來避免使用消費者密鑰,這樣您就可以在 500px.com 上給自己的喜愛的照片投票,或者您可以為評論頁面添加頁面,讓用戶體驗更加美好~我相信您還能想出更多的點子!

我希望你能夠喜歡這個教程,如果您對本教程或者 Alamofire 有任何疑問,請加入到我們的討論當中來!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved