深入理解Swift中單例模式的替換及Swift 3.0單例模式的實(shí)現(xiàn)
前言
除了 MVC、MVVM 之外,單例模式可以說(shuō)是 iOS 開(kāi)發(fā)中另一常見(jiàn)的設(shè)計(jì)模式。無(wú)論是 UIKit 或是一些流行的三方庫(kù),我們都能看到單例的身影。而我們開(kāi)發(fā)者本身也會(huì)潛意識(shí)地將這些類庫(kù)中的代碼當(dāng)作最佳實(shí)踐并將其帶入日常工作中,哪怕很多人都知道單例存在一些明顯的缺陷。
針對(duì)單例的缺陷,本文將介紹一些替換或改造單例模式的方法來(lái)提升代碼質(zhì)量。
單例的優(yōu)點(diǎn)
除了上面提到的模仿最佳實(shí)踐之外,單例的流行肯定也有內(nèi)在的原因和理由。例如:?jiǎn)卫龑?duì)象保證了只有一個(gè)實(shí)例的存在,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息。這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理。 另一方面,全局單一對(duì)象也減少了不必要的對(duì)象創(chuàng)建和銷毀動(dòng)作提高了效率。
下面是一個(gè)典型的單例模式代碼:
class UserManager { static let shared = UserManager() private init() { // 單例模式,防止出現(xiàn)多個(gè)實(shí)例 } .... } extension UserManager { func logOut( ) { ... } func logIn( ) { ... } } class ProfileViewController: UIViewController { private lazy var nameLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() nameLabel.text = UserManager.shared.currentUser?.name } private func handleLogOutButtonTap() { UserManager.shared.logOut() } }
單例的缺陷
雖然上面提到了單例的一些優(yōu)點(diǎn),但是這不能掩蓋單例模式一些明顯的缺陷:
- 全局共享可修改的狀態(tài):?jiǎn)卫J降母弊饔弥痪褪悄切┕蚕頎顟B(tài)量在 app 的生命周期內(nèi)都可能發(fā)生修改,而這些修改可能造成一些位置錯(cuò)誤。更為糟糕的是因?yàn)樽饔糜蚝蜕芷诘奶匦裕@些問(wèn)題還非常難定位。
- 依賴關(guān)系不明確:因?yàn)閱卫谌侄挤浅H菀走M(jìn)行訪問(wèn),這將是我們的代碼變成所謂的 意大利面條 式的代碼。單例與使用者的關(guān)系界限不明確,后期維護(hù)也非常麻煩。
- 難以追蹤測(cè)試:因?yàn)閱卫J脚c app 擁有同樣的生命周期而生命周期內(nèi)進(jìn)行的任意修改,所以無(wú)法確保一個(gè)干凈的實(shí)例用于測(cè)試。
- 由于單利模式中沒(méi)有抽象層,因此單例類的擴(kuò)展有很大的困難。
- 單例類的職責(zé)過(guò)重,在一定程度上違背了 “單一職責(zé)原則”。
依賴注入
與之間之間使用單例對(duì)象不同,這里我們可以在初始化是進(jìn)行依賴注入。
class ProfileViewController: UIViewController { private let user: User private let logOutService: LogOutService private lazy var nameLabel = UILabel() init(user: User, logOutService: LogOutService) { self.user = user self.logOutService = logOutService super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() nameLabel.text = user.name } private func handleLogOutButtonTap() { logOutService.logOut() } } class LogOutService { private let user: User private let networkService: NetworkService private let navigationService: NavigationService init(user: User, networkService: NetworkService, navigationService: NavigationService) { self.user = user self.networkService = networkService self.navigationService = navigationService } func logOut() { networkService.request(.logout(user)) { [weak self] in self?.navigationService.showLoginScreen() } } }
上面代碼中的依賴關(guān)系明顯比之前更為清晰,而且也更方便后期維護(hù)和編寫測(cè)試實(shí)例。另外,通過(guò) LogOutService 對(duì)象我們將某些特定服務(wù)抽離了出來(lái),避免了單例中常見(jiàn)的臃腫狀態(tài)。
協(xié)議化改造
將一個(gè)單例濫用的應(yīng)用一次性全面改寫為上面那樣的依賴注入和服務(wù)化顯然是一件非常耗時(shí)且不合理的事情。所以下面將會(huì)介紹通過(guò)協(xié)議對(duì)單例進(jìn)行逐步改造的方法,這里主要的做法就是將上面 LogOutService 提供的服務(wù)改寫為協(xié)議:
protocol LogOutService { func logOut() } protocol NetworkService { func request(_ endpoint: Endpoint, completionHandler: @escaping () -> Void) } protocol NavigationService { func showLoginScreen() func showProfile(for user: User) ... }
定義好協(xié)議服務(wù)之后,我們讓原有的單例遵循該協(xié)議。此時(shí)我們可以在不修改原有代碼實(shí)現(xiàn)的同時(shí)將單例對(duì)象當(dāng)作服務(wù)進(jìn)行依賴注入。
extension UserManager: LoginService, LogOutService {} extension AppDelegate: NavigationService { func showLoginScreen() { navigationController.viewControllers = [ LoginViewController( loginService: UserManager.shared, navigationService: self ) ] } func showProfile(for user: User) { let viewController = ProfileViewController( user: user, logOutService: UserManager.shared ) navigationController.pushViewController(viewController, animated: true) } }
Swift3.0 單例模式實(shí)現(xiàn)的幾種方法-Dispatch_Once
在開(kāi)發(fā)中需要使用單例模式是再尋常不過(guò)的了,正常我們的思路是使用GCD的dispatch_once這個(gè)API來(lái)寫,然而在swift3.0中,蘋果已經(jīng)廢棄了這個(gè)方法,不過(guò)不用擔(dān)心,我們可以用別的方式來(lái)實(shí)現(xiàn)。
結(jié)合swift語(yǔ)言的特性,總結(jié)了以下幾種寫法:
- 普通創(chuàng)建法
- 靜態(tài)創(chuàng)建法
- struct創(chuàng)建法
- 通過(guò)給DIspatchQueue添加擴(kuò)展實(shí)現(xiàn)
注意:這里我希望大家除了使用還要會(huì)調(diào)用該對(duì)應(yīng)的方法
1.普通創(chuàng)建法
//MARK - : 單例:方法1 static let shareSingleOne = Single()
2.靜態(tài)創(chuàng)建法
let single = Single() class Single: NSObject { //-MARK: 單例:方法2 class var sharedInstance2 : Single { return single } }
3.struct創(chuàng)建法
//-MARK: 單例:方法3 static var shareInstance3:Single{ struct MyStatic{ static var instance :Single = Single() } return MyStatic.instance; }
4.通過(guò)給DispatchQueue添加擴(kuò)展實(shí)現(xiàn)
public extension DispatchQueue { private static var _onceTracker = [String]() /** Executes a block of code, associated with a unique token, only once. The code is thread safe and will only execute the code once even in the presence of multithreaded calls. - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID - parameter block: Block to execute once */ public class func once(token: String, block:()->Void) { objc_sync_enter(self) defer { objc_sync_exit(self) } if _onceTracker.contains(token) { return } _onceTracker.append(token) block() } }
使用字符串token作為once的ID,執(zhí)行once的時(shí)候加了一個(gè)鎖,避免多線程下的token判斷不準(zhǔn)確的問(wèn)題。
使用的時(shí)候可以傳token
DispatchQueue.once(token: "com.vectorform.test") { print( "Do This Once!" ) }
或者使用UUID也可以:
private let _onceToken = NSUUID().uuidString DispatchQueue.once(token: _onceToken) { print( "Do This Once!" ) }
結(jié)語(yǔ)
單例模式并不是毫無(wú)可取之處,例如在日志服務(wù)、外設(shè)管理等場(chǎng)景下還是非常適用的。但是大多數(shù)時(shí)候單例模式由于依賴關(guān)系不明確以及全局共享可變狀態(tài)可能會(huì)增加系統(tǒng)的復(fù)雜度造成一系列未知問(wèn)題。如果你當(dāng)前的代碼中使用了大量的單例模式的話,我希望本文能夠幫你從中解脫出來(lái)構(gòu)建一個(gè)更健壯的系統(tǒng)。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
SwiftUI智能家居開(kāi)關(guān)燈頁(yè)面搭建示例
這篇文章主要為大家介紹了SwiftUI智能家居開(kāi)關(guān)燈頁(yè)面搭建示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08詳解Swift的switch...case語(yǔ)句中break關(guān)鍵字的用法
這篇文章主要介紹了Swift的switch...case語(yǔ)句中break關(guān)鍵字的用法,是Swift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-04-04深入解析Swift中switch語(yǔ)句對(duì)case的數(shù)據(jù)類型匹配的支持
這篇文章主要介紹了Swift中switch語(yǔ)句對(duì)case的數(shù)據(jù)類型匹配的支持,Swift中switch...case語(yǔ)句支持多種數(shù)據(jù)類型的匹配判斷,十分強(qiáng)大,需要的朋友可以參考下2016-04-04Swift 中如何使用 Option Pattern 改善可選項(xiàng)的 API 設(shè)計(jì)
這篇文章主要介紹了Swift 中如何使用 Option Pattern 改善可選項(xiàng)的 API 設(shè)計(jì),幫助大家更好的進(jìn)行ios開(kāi)發(fā),感興趣的朋友可以了解下2020-10-10Swift之UITabBarController 導(dǎo)航控制器的自定義
本文給大家介紹swift導(dǎo)航控制器之UITabBarController,本文通過(guò)代碼實(shí)例給大家講解swift導(dǎo)航控制器,導(dǎo)航控制器類繼承UITabBarController,代碼簡(jiǎn)單易懂,需要的朋友可以參考下2015-10-10