我們可以新建一個檔案FlickrModel.swift
,先不繼承任何類別,但是需要先載入以下的 Framework。
import Foundation
import Alamofire
import SwiftyJSON
class FlickrModel {
}
Alamofire
是一個用做網路溝通協定的一個第三方 Library,而SwiftyJSON
則是用來解析 JSON 格式的一個 Library。
現在我們用 FlickrModel
來管理照片數據與處理 Flickr 圖床的網絡請求,FlickrAPI
有 FLickr 的 API 請求資訊,專案中的其他類別不應該也不需要知道這些邏輯。他們只需要知道 FlickrModel
這個「外觀」就可以了。
為了實現外觀模式,應該只讓 FlickrModel
持有 PhotoVO
的陣列實體資料,以及其他會需要紀錄與儲存的變數,然後 FlickrModel
暴露最少只需要給其他類別來使用的方法,這樣外部的訪問類別不需要知道內部的商業邏輯具體是怎樣的,也不用知道你是通過 Alamofire
還是 FlickrAPI
獲取到數據的。
FlickrModel
會暴露給其他類別訪問,但是其他全部的會用到的屬性則是不對外開放的。
打開 FlickrModel.swift
然後添加如下程式碼:
private var mPhotoVOs :[UInt:[PhotoVO]] = [UInt:[PhotoVO]]()
private var mCurrentPageIndex :UInt = 1
private var mIsNight :Bool = false
private let mPerPage :UInt = 99
mPhotoVOs:[UInt:[PhotoVO]]
只是用來儲存這隻 App 裡面會用到的每一頁的 Flickr 圖片資料,mCurrentPageIndex:UInt
則是用來紀錄 Flickr 的目前顯示與載入的資料是哪一頁,mPerPage:UInt
則是表示目前一頁會有幾張照片的常數。
我們再多寫一個方法,用來儲存 PhotoVO 的字典資料。
func addPhoto(aPageIndex:UInt,aPhotoVOs: [PhotoVO]) {
mPhotoVOs.updateValue(aPhotoVOs, forKey: aPageIndex)
}
當然,也要開放權限讓其他類別可以讀取,所以我們再添加兩個讓外部讀取 VO 的方法。
func getPhotos(aPageIndex:UInt) -> [PhotoVO]{
return mPhotoVOs[aPageIndex]!
}
func getCurrentPhotos() -> [PhotoVO]{
return mPhotoVOs[mCurrentPageIndex]!
}
我們需要在FlickrModel
裡面將FlickrAPI
的下載的資料轉換為我們需要的數據格式PhotoVO
。
func loadFlickrData(aPageIndex:UInt){
Alamofire.request(Method.GET, FlickrAPI.flickrAPIUrl(), parameters: FlickrAPI.flickrInterestingness(aPageIndex, aPerPage: mPerPage), encoding: ParameterEncoding.URL, headers: nil)
.responseJSON(completionHandler: onLoadFlickrDataCompleteHandler)
}
private func onLoadFlickrDataCompleteHandler(aRequest:NSURLRequest?, aResponse:NSHTTPURLResponse?, aResult:Result<AnyObject>){
var _vos:[PhotoVO] = [PhotoVO]()
var _pageIndex:UInt = 0
if aResult.error == nil {
let _json:JSON = JSON(aResult.value!)
if let _items:Array = _json["photos"]["photo"].array {
for _item:JSON in _items {
let _vo:PhotoVO = PhotoVO(
aID : _item["id"].stringValue,
aTitle : _item["title"].stringValue,
aOwner : _item["owner"].stringValue,
aURLQ : _item["url_q"].stringValue,
aURLZ : _item["url_z"].stringValue,
aWidth : _item["width_z"].floatValue,
aHeight : _item["height_z"].floatValue)
_vos.append(_vo)
}
_pageIndex = _json["photos"]["page"].uIntValue
addPhoto(_pageIndex, aPhotoVOs: _vos)
}
print("LoadFlickrDataComplete!count:\(_vos.count)")
}else{
print("loadFlickrData Error:\(aResult.error)")
}
}
看一下 loadFlickrData(aPageIndex:UInt)
這個方法,先透過FlickrAPI
撈取到 JSON 格式的圖庫資料,然後再取出裡面的 ["photos"]["photo"]標籤裡面的陣列。
在歷遍這個圖片資訊的陣列,逐筆建立我們所會用到的PhotoVO
格式,再儲存到我們做好的FlickrModel
裡面。
這便是外觀模式的強大之處:如果外部類別想要做 Flickr 圖床的資料操作,它不會也不用去瞭解FlickrModel
內部的實現邏輯是怎麼樣的,只需要知道FlickrModel
儲存照片資料,照片資料格式為PhotoVO
。
可以再從FlickrModel
的角色再深入去思考,對於其他的類別,也不需要知道目前有哪些照片來源,如何得到的,只要知道FlickrModel
整合好圖片資訊即可。
注意:當你設計外觀的時候,請務必牢記:使用者隨時可能直接訪問你的隱藏類別。永遠不要假設使用者會遵循你當初的設計做事。
我們再打開PhotoView.swift
,幫 PhotoView 多一個下載圖片的功能,而且為了日後可以重複利用,降低耦合的前提下,我們要給它的功能是,給 PhotoView 圖片網址就可以下載圖片,而不是給 PhotoVO 這個圖片描述的 Variable Object。
我們先設定一個全域變數mImageRequest
,這樣圖片的下載階段我們才可以控制下載的取消與重新下載。
private var mImageRequest :Request?
然後再完成downloadImage:
。
func downloadImage(aImageURL:String?,aCacheFileName:String?){
mImageView.image = nil
mImageRequest?.cancel()
if aImageURL != nil {
mLoadingView.startAnimating()
mImageRequest = Alamofire.request(Method.GET, aImageURL!).response { (aRequest:NSURLRequest?, aResponse:NSHTTPURLResponse?, aData:NSData?, aError:ErrorType?) -> Void in
if aError == nil {
if let _image:UIImage = UIImage(data: aData!){
self.mImageView.image = _image
}else{
print("no image")
}
}else{
print("error:\(aError!)")
}
}
}
}
除此以外,讓PhotoView
除了網址以外,也可以直接呈現 UIImage
的功能,所以我們再追加一個程式。
func setImage(aImage:UIImage){
mImageView.image = aImage
}
這樣,我們的PhotoView
也是一個對於這支應用程式而言,一個低耦合,甚至可以直接給其他專案使用的類別,而且他可以自己處理 UIImage
的呈現,也可以直接給圖片網址自行下載的功能。
FlickrModel
以及PhotoView
這兩個類別,就是外觀模式的應用。
完成到這一步的Demo: