可以看下這個圖:
這是一個日誌類,有一個屬性 (是一個單例對象) 和兩個方法 (sharedInstance()
和 init()
)。
第一次調用 sharedInstance()
的時候,instance
屬性還沒有初始化。所以我們要創建一個新實例並且返回。
下一次你再調用 sharedInstance()
的時候,instance
已經初始化完成,直接返回即可。這個邏輯確保了這個類別只存在一個實體對象。
依據這個邏輯,我們也可以寫一個單例模式,通過這個類別來管理圖片數據資料。
我們可以新建一個檔案ImageCache.swift
,並添加下面這些程式碼。
class ImageCache {
private static var mInstance:ImageCache?
static func sharedInstance() -> ImageCache {
if mInstance == nil {
mInstance = ImageCache()
}
return mInstance!
}
}
先建立一個名為ImageCache
的類別,再宣告一個名稱為mInstance
的靜態變數,並且型別為ImageCache
自己本身,為可選值,而且雖然是靜態變數,可是存取權限要設定為private
私有的,除了自己以外,其他類別成員都不能夠去操作存取這個靜態變數。
單例設計模式的奧妙,就在於下一步要做的,我們再宣告一個靜態方法sharedInstance()
,回傳值型別也是ImageCache
,並且我們要在裡面加入單例設計模式的主要邏輯。
如果其他類別成員要透過sharedInstance()
這個方法取得ImageCache
的實體時,就會開始判斷mInstance
是否為空值:
單一設計模式下,封裝了兩個邏輯,一個是私有的靜態變數,一個是公開的靜態方法。
私有的靜態變數mInstance
,通過 static
定義意味著這個屬性只存在一個。公開的靜態方法sharedInstance
,直到需要的時候才會被初始化。
同時再注意一下,因為它是一個唯讀的常數,所以一旦建立之後,不會也不能夠再建立第二次,也不能夠被別的值所取代。
這些就是單例模式的核心所在:一旦初始化完成,當前類別存在一個實例物件,初始化方法就不會再被調用。
最後,我們再複寫掉初始化方法,把建構式私有化,讓他不能被其他類別成員用除了靜態方法sharedInstance
以外的方式實體化,以確保單例模式的唯一性。
我們現在可以將這個單例作為圖片資料快取儲存,接下來我們繼續完成他全部的功能。
import Foundation
import UIKit
class ImageCache {
private static var mInstance:ImageCache?
static func sharedInstance() -> ImageCache {
if mInstance == nil {
mInstance = ImageCache()
}
return mInstance!
}
private init(){
}
private var mImages:[String:UIImage] = [String:UIImage]()
func saveImage(aImage:UIImage,aFilename: String){
mImages.updateValue(aImage, forKey: aFilename)
saveLocalImage(aImage,aFilename: aFilename)
}
func getImage(aFilename: String) -> UIImage? {
var _result:UIImage?
if let _dicImage:UIImage = mImages[aFilename] {
_result = _dicImage
}else if let _docImage:UIImage = getLocalImage(aFilename){
_result = _docImage
}
return _result
}
private func saveLocalImage(aImage: UIImage, aFilename: String) {
let _path = NSHomeDirectory().stringByAppendingString("/Documents/\(aFilename)")
let _data:NSData? = UIImagePNGRepresentation(aImage)
_data?.writeToFile(_path, atomically: true)
}
private func getLocalImage(aFilename: String) -> UIImage? {
let _path = NSHomeDirectory().stringByAppendingString("/Documents/\(aFilename)")
do {
let _data = try NSData(contentsOfFile: _path, options: .UncachedRead)
return UIImage(data: _data)
} catch _ {
return nil
}
}
}
在這裡我們定義了一個私有屬性,用來存儲照片資料。這是一個字典變數,所以值為字串,在單例模式下,ImageCache類別只會存在一份實體,同樣的在這類別裡面的mImages:[String:UIImage]
字典裡面,也只會存在一個實體。
我們會把圖片儲存到手機本機端,這樣可以避免一次又一次下載相同的照片。
程式內容很簡單直接,下載的照片會儲存在 Documents 目錄下,如果沒有檢查到暫存檔案, getImage() 方法則會返回 nil 。
我們開始運用看看: 打開PhotoView.swift
這個類別,
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
ImageCache.sharedInstance().saveImage(_image, aFilename: aCacheFileName!)
}else{
print("no image")
}
}else{
//print("error:\(aError!)")
}
}
}
}
下載完圖片以後,直接儲存到圖片快取裡面。
我們使用外觀模式隱藏了下載圖片的複雜程度。通知的發送者並不在乎圖片是如何從網上下載到本地的。
我們先開始做最開始的版型排版:
先打開MainViewController.swift
,開始編輯裡面的viewDidLoad
,並且加ㄧ個變數用來儲存會顯示的全部的PhotoView
,在宣告一個變數用來建立 FlickrModel 的實體。
private var mPhotoViews:[PhotoView] = [PhotoView]()
private let mFlickrModel:FlickrModel = FlickrModel()
然後,我們在寫上測試到目前功能的程式碼:
private func createDemoLayout(){
mFlickrModel.loadFlickrData(1)
for _index:UInt in 0..<10 {
let _photoView :PhotoView = PhotoView()
let _boardWidth :CGFloat = 5
let _dWidth :CGFloat = (self.view.bounds.width - _boardWidth * 5) / 4
let _dHeight :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
let _dX :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
let _dY :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
_photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)
self.view.addSubview(_photoView)
mPhotoViews.append(_photoView)
}
let _button:UIButton = UIButton()
_button.frame = CGRectMake(10, self.view.bounds.height - 30, self.view.bounds.width - 20, 25)
_button.backgroundColor = UIColor.redColor()
self.view.addSubview(_button)
_button.addTarget(self, action: Selector("onTestLoadImageHandler"), forControlEvents: UIControlEvents.TouchUpInside)
}
func onTestLoadImageHandler(){
for _photoView:PhotoView in mPhotoViews {
let _index:Int = mPhotoViews.indexOf(_photoView)!
let _vo:PhotoVO = mFlickrModel.getPhotos(1)[_index]
_photoView.downloadImage(_vo.url_z, aCacheFileName: "_z.png")
}
}
然後,我們在viewDidload執行我們剛剛所寫的程式碼:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
createDemoLayout()
}
而原本測試的 PhotoDelegate
就先刪除吧,避免程式碼的雜亂。
在模擬器中執行,會發現一開始會出現10個整齊排列的藍色框框,這些都是我們寫的 PhotoView,如果網路正常的情況下,會在Xcode 裡的 Console 視窗出現LoadFlickrDataComplete!count:99
,表示 Flickr 的資料載入完成,然後我們再按下最下面的紅色按鈕,載入圖片。
完成到這一步的Demo: