深入理解Swift中單例模式的替換及Swift 3.0單例模式的實現(xiàn)
前言
除了 MVC、MVVM 之外,單例模式可以說是 iOS 開發(fā)中另一常見的設計模式。無論是 UIKit 或是一些流行的三方庫,我們都能看到單例的身影。而我們開發(fā)者本身也會潛意識地將這些類庫中的代碼當作最佳實踐并將其帶入日常工作中,哪怕很多人都知道單例存在一些明顯的缺陷。
針對單例的缺陷,本文將介紹一些替換或改造單例模式的方法來提升代碼質量。
單例的優(yōu)點
除了上面提到的模仿最佳實踐之外,單例的流行肯定也有內(nèi)在的原因和理由。例如:單例對象保證了只有一個實例的存在,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在復雜環(huán)境下的配置管理。 另一方面,全局單一對象也減少了不必要的對象創(chuàng)建和銷毀動作提高了效率。
下面是一個典型的單例模式代碼:
class UserManager {
static let shared = UserManager()
private init() {
// 單例模式,防止出現(xiàn)多個實例
}
....
}
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)點,但是這不能掩蓋單例模式一些明顯的缺陷:
- 全局共享可修改的狀態(tài):單例模式的副作用之一就是那些共享狀態(tài)量在 app 的生命周期內(nèi)都可能發(fā)生修改,而這些修改可能造成一些位置錯誤。更為糟糕的是因為作用域和生命周期的特性,這些問題還非常難定位。
- 依賴關系不明確:因為單例在全局都非常容易進行訪問,這將是我們的代碼變成所謂的 意大利面條 式的代碼。單例與使用者的關系界限不明確,后期維護也非常麻煩。
- 難以追蹤測試:因為單例模式與 app 擁有同樣的生命周期而生命周期內(nèi)進行的任意修改,所以無法確保一個干凈的實例用于測試。
- 由于單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
- 單例類的職責過重,在一定程度上違背了 “單一職責原則”。
依賴注入
與之間之間使用單例對象不同,這里我們可以在初始化是進行依賴注入。
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()
}
}
}
上面代碼中的依賴關系明顯比之前更為清晰,而且也更方便后期維護和編寫測試實例。另外,通過 LogOutService 對象我們將某些特定服務抽離了出來,避免了單例中常見的臃腫狀態(tài)。
協(xié)議化改造
將一個單例濫用的應用一次性全面改寫為上面那樣的依賴注入和服務化顯然是一件非常耗時且不合理的事情。所以下面將會介紹通過協(xié)議對單例進行逐步改造的方法,這里主要的做法就是將上面 LogOutService 提供的服務改寫為協(xié)議:
protocol LogOutService {
func logOut()
}
protocol NetworkService {
func request(_ endpoint: Endpoint, completionHandler: @escaping () -> Void)
}
protocol NavigationService {
func showLoginScreen()
func showProfile(for user: User)
...
}
定義好協(xié)議服務之后,我們讓原有的單例遵循該協(xié)議。此時我們可以在不修改原有代碼實現(xià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 單例模式實現(xiàn)的幾種方法-Dispatch_Once
在開發(fā)中需要使用單例模式是再尋常不過的了,正常我們的思路是使用GCD的dispatch_once這個API來寫,然而在swift3.0中,蘋果已經(jīng)廢棄了這個方法,不過不用擔心,我們可以用別的方式來實現(xiàn)。
結合swift語言的特性,總結了以下幾種寫法:
- 普通創(chuàng)建法
- 靜態(tài)創(chuàng)建法
- struct創(chuàng)建法
- 通過給DIspatchQueue添加擴展實現(xiàn)
注意:這里我希望大家除了使用還要會調(diào)用該對應的方法
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.通過給DispatchQueue添加擴展實現(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的時候加了一個鎖,避免多線程下的token判斷不準確的問題。
使用的時候可以傳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!" )
}
結語
單例模式并不是毫無可取之處,例如在日志服務、外設管理等場景下還是非常適用的。但是大多數(shù)時候單例模式由于依賴關系不明確以及全局共享可變狀態(tài)可能會增加系統(tǒng)的復雜度造成一系列未知問題。如果你當前的代碼中使用了大量的單例模式的話,我希望本文能夠幫你從中解脫出來構建一個更健壯的系統(tǒng)。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
淺析Swift中struct與class的區(qū)別(匯編角度底層分析)
這篇文章主要介紹了Swift中struct與class的區(qū)別 ,本文從匯編角度分析struct與class的區(qū)別,通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03

