【iOS - Singleton(單例設計模式)】
Singleton 是一種設計模式且適用於任何 OOP 的程式語言,它的精神在於節省記憶體的使用量,但這時又會有人說,記憶體真的有珍貴到這種地步嗎?所以,又有另外一種精神衍生出來,那就是唯一性,也就是在整個應用程式的生命週期中永遠都只會存在一個實體,在 iOS 的設計中很常見唯一性,像是 UIApplication,應該是永遠只會有一個吧!再者還有 UIScreen 也是永遠只有一個!所以,當我們在設計類別時,首先應該思考的是它的特性,再來決定我們是否要使用 Singleton 來設計。或許這樣說,大家還不是很清楚,法蘭克就舉幾個適合和不適合的例子來說明好了。
適合:
- 專門用來跟資料庫溝通的類別(DataAccessObject),又或者是處理字串的工具包(StringUtils)。這兩種類型的類別因不管在 App 的哪一個功能裡它都可以是同一個實體。
- 具唯一性的環境配置檔。
不適合:
- 直接舉 Apple 所提供的類別來說明,例如 UIView、UILabel 該類型的類別就不適合使用 Singleton,因為該類型的類別都違反了唯一性,也就是說在同一個畫面裡的每一個 UILabel 都應該會是不同的實體,每個實體在使用前都必須個別生成的。
Singleton 是否符合多執行緒的安全呢?Swift3 前的寫法上(dispatch_once)有些許的不同,但法蘭克就不再追溯之前的寫法了。而提到多執行緒安全,這又衍生出兩個議題,一為在多執行緒下是否都是取到相同的實體呢?;二則為在多執行緒下處理 Singleton 裡的變數是否能保證絕對安全的呢?文章始,會針對前項來撰寫實例證明之,而文末,則來說明如何保證 Singleton 裡的變數是多執緒安全的。
▼產生一個 Single View Application 的專案

▼將專案名稱命名為 SingletonDemo

▼新增一個 DataAccessObject.swift 檔並撰寫 Singleton 的邏輯
如上建立了符合 Singleton 的類別,但法蘭克剛剛在前言有說道在 Swift3 之前為了符合執行緒安全的狀況下(也就是同時開啟多執行緒來取得該物件時,都要取到同一個物件),寫法會有些許的不同(可參考),但在 Swift3 Apple 大大的簡化了其寫法,說明如下。

如上說明,法蘭克使用了 static properties 的修飾字來定義變數,以符合執行緒安全(最後法蘭克會撰寫實例來驗證之),先來說明以上的程式碼範例。
第 5 行 => 透過 sharedInstance 變數來取得該類別的實例。宣告為 static 可直接透過物件 +「.」運算子來取得該實例。
第 7 ~ 9 行 => 初始化這個物件的建構子。在這邊宣告成 private(私有) 是因為只能在該類別裡生成,也就是目前的設計僅能讓 sharedInstance() 來生成,不能從外部去生成它。在這裡必須 override(覆寫) 掉父類別的建構子。
第 11 ~ 13 行 => 該物件銷毀的時侯會執行該區塊的程式碼,用來觀察物件被銷毀的時機點。
第 15~ 17 行 => 用來測試 Singleton 的方法。
▼在 ViewController的ViewDidLoad 方法下開啟三條執行緒來測試是否符合Singleton的設計模式
在 ViewDidLoad 加入以下程式碼後,啟動模擬器來測試看看:
執行的結果 console 會如下:

在這邊我們看到當呼叫 loadDatas() 時會執行 init,當再次呼叫該類的 loadDatas(),並不會再生成新的物件,如此就是 Singleton 的精神所在。最後,法蘭克要來說明 Singleton 如何保證裡頭的變數是多執緒安全的。
▼在 DataAccessObject.swift 檔下撰寫保證裡頭的變數是多執緒安全的的邏輯
第 7 行 => 初始化 DispatchQueue 以用來處理 Singleton 裡保證多執行緒安全的變數。
第 8 行 => getter 和 setter 的變數。
第 10 行=> 宣告一用來被外部類別存取的變數。
第 19~ 23 行 => 使用 Synchronous(同步的) 的方式取得變數。
第 17~ 22 行 => 使用 Asynchronous(非同步的) 的方式設定變數,並使用 barrier flag 告訴佇列,這個特定工作項目,必須在沒有其他平行執行的項目時執行,以確保證執行緒安全。
如果您喜歡我的文章,請多按幾下「拍手」給我鼓勵,或是按「follow」讓我持續提供好文章給您。
