> For the complete documentation index, see [llms.txt](https://wilden-chen.gitbook.io/swift-design-patterns/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wilden-chen.gitbook.io/swift-design-patterns/singleton/use-singleton.md).

# 如何使用單例模式

可以看下這個圖：

![](/files/-Lejh1qOk7qWCeEHkCFx)

這是一個日誌類，有一個屬性 (是一個單例對象) 和兩個方法 (`sharedInstance()` 和 `init()`)。

第一次調用 `sharedInstance()` 的時候， `instance` 屬性還沒有初始化。所以我們要創建一個新實例並且返回。

下一次你再調用 `sharedInstance()` 的時候，`instance` 已經初始化完成，直接返回即可。這個邏輯確保了這個類別只存在一個實體對象。

依據這個邏輯，我們也可以寫一個單例模式，通過這個類別來管理圖片數據資料。

我們可以新建一個檔案 `ImageCache.swift` ，並添加下面這些程式碼。

```swift
class ImageCache {
    static let shared = ImageCache()
    static func sharedInstance() -> ImageCache {
        return shared
    }
    private init() {}
}
```

在現代 Swift 中，宣告單例設計模式極為簡單且安全。我們只需要宣告一個公開的靜態常數 `static let shared = ImageCache()`，並且將初始化建構子 `init()` 設為私有 `private init()`，以確保外部無法透過其他方式實體化該類別。

在 Swift 中，靜態屬性 `static let` 在首次被存取時會自動進行延遲載入 (Lazy Loading)，且底層由 GCD (Grand Central Dispatch) 保證其**執行緒安全 (Thread-Safe)**，讀者不需要再像以前一樣手動檢查 `nil` 並進行初始化。

為了與原本專案的寫法相容，我們另外提供了一個 `sharedInstance()` 靜態方法來回傳這個單例：

這些就是單例模式的核心所在：確保類別在整個應用程式生命週期中僅存在唯一實體。

最後，藉由將 `init()` 方法設為 `private`，限制外部無法使用 `ImageCache()` 實體化，保證了單例模式的唯一性。

我們現在可以將這個單例作為圖片資料快取儲存，接下來我們繼續完成他全部的功能。

```swift
import Foundation
import UIKit

class ImageCache {
    static let shared = ImageCache()
    static func sharedInstance() -> ImageCache {
        return shared
    }
    private init() {

    }

    private var mImages:[String:UIImage] = [String:UIImage]()

    func saveImage(aImage:UIImage,aFilename: String) {
        mImages.updateValue(aImage, forKey: aFilename)
        saveLocalImage(aImage: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:aFilename){
            _result = _docImage
        }
        return _result
    }

    private func saveLocalImage(aImage: UIImage, aFilename: String) {
        guard let _documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
        let _fileURL = _documentsURL.appendingPathComponent(aFilename)
        let _data = aImage.pngData()
        do {
            try _data?.write(to: _fileURL, options: .atomic)
        } catch {
            print(error)
        }
    }

    private func getLocalImage(aFilename: String) -> UIImage? {
        guard let _documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
        let _fileURL = _documentsURL.appendingPathComponent(aFilename)
        do {
            let _data = try Data(contentsOf: _fileURL)
            return UIImage(data: _data)
        } catch {
            return nil
        }
    }
}
```

在這裡我們定義了一個私有屬性，用來存儲照片資料。這是一個字典變數，所以值為字串，在單例模式下，ImageCache 類別只會存在一份實體，同樣的在這類別裡面的`mImages:[String:UIImage]` 字典裡面，也只會存在一個實體。

我們會把圖片儲存到手機本機端，這樣可以避免一次又一次下載相同的照片。

程式內容很簡單直接，下載的照片會儲存在 Documents 目錄下，如果沒有檢查到暫存檔案， getImage() 方法則會返回 nil 。

我們開始運用看看： 打開`PhotoView.swift`這個類別，

```swift
    func downloadImage(aImageURL:String?,aCacheFileName:String?) {
        mImageView.image = nil
        if let _url = aImageURL, let _cacheName = aCacheFileName {
            mLoadingView.startAnimating()
            AF.request("\(_url)\(_cacheName)").response { aResponse in
                if aResponse.error == nil {
                    if let _data = aResponse.data, let _image = UIImage(data: _data) {
                        self.mImageView.image = _image
                        ImageCache.sharedInstance().saveImage(aImage: _image, aFilename: _cacheName)
                    } else {
                        print("no image")
                    }
                } else {
                    print("error:\(aResponse.error?.localizedDescription ?? "")")
                }
            }
        }
    }
```

下載完圖片以後，直接儲存到圖片快取裡面。

我們使用外觀模式隱藏了下載圖片的複雜程度。通知的發送者並不在乎圖片是如何從網上下載到本地的。

我們先開始做最開始的版型排版：

先打開 `MainViewController.swift` ，開始編輯裡面的 `viewDidLoad` ，並且加ㄧ個變數用來儲存會顯示的全部的 `PhotoView` ，在宣告一個變數用來建立 FlickrModel 的實體。

```swift
    private var mPhotoViews :[PhotoView] = [PhotoView]()
    private let mFlickrModel:FlickrModel = FlickrModel()
```

然後，我們在寫上測試到目前功能的程式碼：

```swift
    private func createDemoLayout() {
        mFlickrModel.loadFlickrData(aPageIndex: 1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5.0
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5.0)  / 4.0
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6.0) / 5.0
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50.0 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRect(x: _dX, y: _dY, width: _dWidth, height: _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
        }

        let _button:UIButton = UIButton()
        _button.frame = CGRect(x: 10.0, y: self.view.bounds.height - 30.0, width: self.view.bounds.width - 20.0, height: 25.0)
        _button.backgroundColor = UIColor.red
        self.view.addSubview(_button)

        _button.addTarget(self, action: #selector(self.onTestLoadImageHandler), for: .touchUpInside)
    }

    func onTestLoadImageHandler(){
        for _photoView:PhotoView in mPhotoViews {
            let _index:Int = mPhotoViews.firstIndex(of: _photoView)!
            let _vo:PhotoVO = mFlickrModel.getPhotos(aPageIndex: 1)[_index]
            _photoView.downloadImage(aImageURL: _vo.url, aCacheFileName: ".jpg")
        }
    }
```

然後，我們在 viewDidload 執行我們剛剛所寫的程式碼：

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.white

        createDemoLayout()
    }
```

而原本測試的 `PhotoDelegate`就先刪除吧，避免程式碼的雜亂。

在模擬器中執行，會發現一開始會出現 10 個整齊排列的藍色框框，這些都是我們寫的 PhotoView ，如果網路正常的情況下，會在 Xcode 裡的 Console 視窗出現 `LoadFlickrDataComplete!count:99` ，表示 Flickr 的資料載入完成，然後我們再按下最下面的紅色按鈕，載入圖片。

![](/files/-Lejh1qTY_W5PkPg2lbk)

完成到這一步的Demo：

* 查看原始碼
* [下載ZIP](https://github.com/wildenchen/swift-bethel-of-the-road/tree/eed3c8ff6c1141681b3d3b2c69fac406cd11c24c/DesignPatterns/workspaces/FlickrPhotos-Singleton.zip)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wilden-chen.gitbook.io/swift-design-patterns/singleton/use-singleton.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
