深入理解Swift中单例模式的替换及Swift 3.0单例模式的实现

2020-01-08 23:52:00王冬梅

前言

除了 MVC、MVVM 之外,单例模式可以说是 iOS 开发中另一常见的设计模式。无论是 UIKit 或是一些流行的三方库,我们都能看到单例的身影。而我们开发者本身也会潜意识地将这些类库中的代码当作最佳实践并将其带入日常工作中,哪怕很多人都知道单例存在一些明显的缺陷。

针对单例的缺陷,本文将介绍一些替换或改造单例模式的方法来提升代码质量。

单例的优点

除了上面提到的模仿最佳实践之外,单例的流行肯定也有内在的原因和理由。例如:单例对象保证了只有一个实例的存在,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。 另一方面,全局单一对象也减少了不必要的对象创建和销毁动作提高了效率。

下面是一个典型的单例模式代码:


class UserManager {
 static let shared = UserManager()
 
 private init() {
 // 单例模式,防止出现多个实例
 }
 
 ....
}

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()
 }
}

单例的缺陷

虽然上面提到了单例的一些优点,但是这不能掩盖单例模式一些明显的缺陷:

全局共享可修改的状态:单例模式的副作用之一就是那些共享状态量在 app 的生命周期内都可能发生修改,而这些修改可能造成一些位置错误。更为糟糕的是因为作用域和生命周期的特性,这些问题还非常难定位。 依赖关系不明确:因为单例在全局都非常容易进行访问,这将是我们的代码变成所谓的 意大利面条 式的代码。单例与使用者的关系界限不明确,后期维护也非常麻烦。 难以追踪测试:因为单例模式与 app 拥有同样的生命周期而生命周期内进行的任意修改,所以无法确保一个干净的实例用于测试。 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 单例类的职责过重,在一定程度上违背了 “单一职责原则”。

依赖注入

与之间之间使用单例对象不同,这里我们可以在初始化是进行依赖注入。


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()
 }
 }
}