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

前言

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

上面代码中的依赖关系明显比之前更为清晰,而且也更方便后期维护和编写测试实例。另外,通过 LogOutService 对象我们将某些特定服务抽离了出来,避免了单例中常见的臃肿状态。

协议化改造

将一个单例滥用的应用一次性全面改写为上面那样的依赖注入和服务化显然是一件非常耗时且不合理的事情。所以下面将会介绍通过协议对单例进行逐步改造的方法,这里主要的做法就是将上面 LogOutService 提供的服务改写为协议:

protocol LogOutService {
 func logOut()
}

protocol NetworkService {
 func request(_ endpoint: Endpoint, completionHandler: @escaping () -> Void)
}

protocol NavigationService {
 func showLoginScreen()
 func showProfile(for user: User)
 ...
}

定义好协议服务之后,我们让原有的单例遵循该协议。此时我们可以在不修改原有代码实现的同时将单例对象当作服务进行依赖注入。

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 单例模式实现的几种方法-Dispatch_Once

在开发中需要使用单例模式是再寻常不过的了,正常我们的思路是使用GCD的dispatch_once这个API来写,然而在swift3.0中,苹果已经废弃了这个方法,不过不用担心,我们可以用别的方式来实现。

结合swift语言的特性,总结了以下几种写法:

  • 普通创建法
  • 静态创建法
  • struct创建法
  • 通过给DIspatchQueue添加扩展实现

注意:这里我希望大家除了使用还要会调用该对应的方法

1.普通创建法

//MARK - : 单例:方法1
 static let shareSingleOne = Single()

2.静态创建法

let single = Single()
class Single: NSObject {
 //-MARK: 单例:方法2
 class var sharedInstance2 : Single {
  return single
 }
}

3.struct创建法

 //-MARK: 单例:方法3
 static var shareInstance3:Single{
 struct MyStatic{
  static var instance :Single = Single()
 }
 return MyStatic.instance;
 }

4.通过给DispatchQueue添加扩展实现

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,执行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!" )
} 

结语

单例模式并不是毫无可取之处,例如在日志服务、外设管理等场景下还是非常适用的。但是大多数时候单例模式由于依赖关系不明确以及全局共享可变状态可能会增加系统的复杂度造成一系列未知问题。如果你当前的代码中使用了大量的单例模式的话,我希望本文能够帮你从中解脱出来构建一个更健壮的系统。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • IOS Swift3 四种单例模式详解及实例

    Swift3 单例模式 常见的有这么几种方法 第一种简单到爆的 final class Single: NSObject { static let shared = Single() private override init() {} } final关键字的作用是这个类或方法不希望被继承和重写 第二种 public extension DispatchQueue { private static var onceToken = [String]() public class func once

  • 通过示例分析Swift单例模式

    三种Swift实现单例模式的方法:全局变量,内部变量,dispatch_once方式 1. 全局变量 private let _singleton = Singleton() class Singleton: NSObject { class var sharedInstance: Singleton { get { return _singleton } } } 2. 内部变量 class Singleton { class var sharedInstance: Singleton { ge

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

    前言 除了 MVC.MVVM 之外,单例模式可以说是 iOS 开发中另一常见的设计模式.无论是 UIKit 或是一些流行的三方库,我们都能看到单例的身影.而我们开发者本身也会潜意识地将这些类库中的代码当作最佳实践并将其带入日常工作中,哪怕很多人都知道单例存在一些明显的缺陷. 针对单例的缺陷,本文将介绍一些替换或改造单例模式的方法来提升代码质量. 单例的优点 除了上面提到的模仿最佳实践之外,单例的流行肯定也有内在的原因和理由.例如:单例对象保证了只有一个实例的存在,这样有利于我们协调系统整体的行为

  • 初步理解Swift中的泛型

    如果你已经动手写过Swift的程序,相信你已经了解了Swift语言的知识,比如如何写类(class)和结构体(struct).但Swift可没这么简单,呵呵呵.这篇教程主要讲述Swift的一个强力的特性:泛型.这个特性在很多程序设计语言里都非常受欢迎. 对于类型安全(type-safe)语言,一个常见的问题就是如何编写适用于多种类型输入的程序.想象一下,两个整型数相加和两个浮点数相加的程序看起来应该非常类似,甚至一模一样才对.唯一的区别就是变量的类型不同. 在强类型语言中,你需要去定义诸如add

  • 详解Swift中的函数及函数闭包使用

    一.引言 函数是有特定功能的代码段,函数会有一个特定的名称调用时来使用.Swift提供了十分灵活的方式来创建与调用函数.事实上在Swift,每个函数都是一种类型,这种类型由参数和返回值来决定.Swift和Objective-C的一大区别就在于Swift中的函数可以进行嵌套. 而Swift中的闭包是有一定功能的代码块,这十分类似于Objective-C中的block语法.Swift中的闭包语法风格十分简洁,其作用和函数的作用相似. 二.函数的创建与调用 函数通过函数名,参数和返回值来定义,参数和返

  • Swift中定义单例的方法实例

    什么是单例 单例模式是设计模式中最简单的一种,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象. 单例模式(Singleton Pattern),也叫单子模式,是一种常用的软件设计模式. 在应用这个模式时,单例对象的类必须保证只有一个实例存在. 单实例Singleton设计模式可能是被讨论和使用的最广泛的一个设计模式了,这可能也是面试中问得最多的一个设计模式了.这个设计模式主要目的是想在整个系统中只能出现一个类的实例.这样做当然

  • 深入理解Swift中的Substring和String

    前言 为文本字符串添加特性或者语法糖在各种编程语言中都很普遍.就拿大家都很熟悉的 C 语言举例,C 字符串本质是一个字符数组(characters array),但是每次输入字符串的时候不用输入 ['h','e','l','l','o'] ,直接打 hello 就可以了,因为这个操作编译器帮你做了. 更高级的语言比如 Swift 处理字符串就不仅仅是当做字符数组了,String 是一个完整的类型,并且有各种特性.我们先来看一下 String 的一个特性:substring. 简单的看一下 Str

  • 深入理解Swift中的变量与常量

    前言 最近在学习Swift这门新语言,对于熟练掌握OC编程的iOS开发者来说其实很容易上手,但Swift的确在语法和编程习惯上改变了很多,对于从未了解OC语言而从Swift开始学习iOS开发的新手来说可能上手有一定难度,下面我将这段时间的学习成果结合在网上搜索的知识做一个简单的汇总,希望可以帮助到正在学习Swift的小伙伴们. 变量和常量的定义 Swift开发文档中是这样定义变量和常量的:常量和变量把名字和一个特定类型的值关联起来.常量的值一旦设置好便不能再被更改,然而变量可以在将来被设置为不同

  • swift中的正则表达式小结

    作为一门先进的编程语言,Swift 可以说吸收了众多其他先进语言的优点,但是有一点却是让人略微失望的,就是 Swift 至今为止并没有在语言层面上支持正则表达式. 正则表达式的用处: 判断给定的字符串是否符合某一种规则(专门用于操作字符串) - 电话号码,电子邮箱,URL... - 可以直接百度别人写好的正则 - 别人真的写好了,而且测试过了,我们可以直接用 - 要写出没有漏洞正则判断,需要大量的测试,通常最终结果非常负责 过滤筛选字符串,网络爬虫 替换文字,QQ聊天,图文混排 语法规则 使用过

  • 在Swift中如何使用正则表达式详解

    前言 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表通常被用来检索.替换那些符合某个模式(规则)的文本. 正则表达式(Regular expression, regex)允许我们在几秒钟内在成千上万文档间进行复杂检索与替换,自从诞生50多年来它依旧广泛使用. Swift虽然是一个新出的语言,但却不提供专门的处理正则的语法和类.所以我们只能使用古老的NSRegularExpression类进行

  • Swift中通知中心(NotificationCenter)的使用示例

    前言 本文主要介绍了关于Swift通知中心(NotificationCenter)使用的相关内容,NotificationCenter是Swift中一个调度消息通知的类,采用单例模式设计,实现传值.回调等作用. 通知的作用还是挺强大的,对于两个不相关的控制器之间,要进行信息的传递,使用通知是个不错的选择,下面话不多说了,来一起看看详细的使用方法吧. 1.添加通知 /// 通知名 let notificationName = "XMNotification" /// 自定义通知 Noti

  • Swift中动态调用实例方法介绍

    在 Swift 中有一类很有意思的写法,可以让我们不直接使用实例来调用这个实例上的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法.比如我们有这样的定义: 复制代码 代码如下: class MyClass {     func method(number: Int) -> Int {         return number + 1     } } 想要调用 method 方法的话,最普通的使用方式是生成MyClass的实例,然后用.method来

随机推荐