转自:http://www.jianshu.com/p/e36c15a28347
如有侵犯,请来信oiken@qq.com
单例模式概念
单例模式:保证一个类仅有一个实例,并提供一个可以访问它的全局访问点
使用场景:我在书中看到了这些话:当有一个实例,在整个应用中都不想被复制的时候;希望使用它来代表一个真实世界的资源时(例如:Server);用来统计一系列相关的活动时(logging)。总而言之,在关于一些共享资源或者控制资源的时候,可以考虑使用Singleton。
一些错误范例
范例一:
//消息内容
class DataItem {
enum ItemType : String {
case Email = "Email Address";
case Phone = "Telephone Number";
case Card = "Credit Card Number";
}
var type:ItemType;
var data:String;
init(type:ItemType, data:String) {
self.type = type; self.data = data;
}
}
//Server
class BackupServer {
let name:String;
private var data = [DataItem]();
init(name:String) {
self.name = name;
}
func backup(item:DataItem) {
data.append(item);
}
func getData() -> [DataItem]{
return data;
}
}
在main.Swift
中进行测试
//向Server中添加数据
var server = BackupServer(name:"Server#1");
server.backup(DataItem(type: DataItem.ItemType.Email, data: "joe@example.com"));
server.backup(DataItem(type: DataItem.ItemType.Phone, data: "555-123-1133"));
var otherServer = BackupServer(name:"Server#2");
otherServer.backup(DataItem(type: DataItem.ItemType.Email, data: "bob@example.com"));
分析:这些代码可以正常执行,但是没有任何实际的意义。在现实生活中,服务器是昂贵的,不可能就通过程序员创建一个对象来提供一个新的服务器。我们的数据肯定都是放在一个共同服务器上,那么我们在整个程序中只创建一个对象就可以了,与我们单例模式的思想相符。
范例二:
class Logger {
private var data = [String]()
func log(msg:String) {
data.append(msg);
}
func printLog() {
for msg in data {
println("Log: \(msg)");
}
} }
在main.swift
中进行测试:
let logger = Logger();
var server = BackupServer(name:"Server#1");
server.backup(DataItem(type: DataItem.ItemType.Email, data: "joe@example.com"));
server.backup(DataItem(type: DataItem.ItemType.Phone, data: "555-123-1133"));
logger.log("Backed up 2 items to \(server.name)");
var otherServer = BackupServer(name:"Server#2");
otherServer.backup(DataItem(type: DataItem.ItemType.Email, data: "bob@example.com"));
logger.log("Backed up 1 item to \(otherServer.name)");
logger.printLog();
运行上面的程序可以看到打印的结果:
Log: Backed up 2 items to Server#1
Log: Backed up 1 item to Server#2
这个程序像期待的那样正常运行,使用Logger的本地实例来记录一些debug信息,同时调用printLog
来打印一些关于已经backed up
的数据的信息。
假如这时候也想要在BackupServer
类log一些debug信息
class BackupServer {
let name:String;
private var data = [DataItem](); let logger = Logger();
init(name:String) {
self.name = name;
logger.log("Created new server \(name)");
}
func backup(item:DataItem) {
data.append(item);
logger.log("\(name) backed up item of type \(item.type.rawValue)");
}
func getData() -> [DataItem]{
return data;
} }
这个时候有两个Logger
对象,每一个都存放着一些debug信息。当我们再次运行程序的时候,发现main.Swift
中的printLog
只打印了main.swift
对象记录的信息,而对于BackupServer
类中的信息都没有打印。
从这里可以知道,一系列的debug信息,完全没有必要创建多个对象来进行记录,那么下面使用单例模式来重新完善程序。
单例规则
- 单例必须在程序生命周期中是唯一的,例如:
NSNotificationCenter
、UIApplication
和NSUserDefaults
- 为了保证单例是唯一的,初始化方法必须是私有的。
- 单例必须是线程安全的。在Object-C中单例通常是用
dispatch_once
来实现的,在Swift中同样可以这么做。但是由于Swift中支持全局变量、常量的lazy初始化,我们可以简化单例的实现:通过let定义单例实例常量来保证线程安全,通过全局常量的lazy初始化来保证单例只初始化一次。[全局变量(还有结构体和枚举体的静态成员)的Lazy初始化方法会在其被访问的时候调用一次]
Tips:单例模式只适用于类这样的引用类型,对于结构体和其他值类型并不合适。
全局变量方法实现单例
let globalLogger = Logger();
final class Logger {
private var data = [String]()
private init() {
// do nothing - required to stop instances being
// created by code in other files
}
func log(msg:String) {
data.append(msg);
}
func printLog() {
f or msg in data {
println("Log: \(msg)");
}
} }
改写BackupServer
class BackupServer {
let name:String;
private var data = [DataItem]();
init(name:String) {
self.name = name;
globalLogger.log("Created new server \(name)");
}
func backup(item:DataItem) {
data.append(item);
globalLogger.log("\(name) backed up item of type \(item.type.rawValue)");
}
func getData() -> [DataItem]{
return data;
} }
改写main.Swift
var server = BackupServer(name:"Server#1");
server.backup(DataItem(type: DataItem.ItemType.Email, data: "joe@example.com"));
server.backup(DataItem(type: DataItem.ItemType.Phone, data: "555-123-1133"));
globalLogger.log("Backed up 2 items to \(server.name)");
var otherServer = BackupServer(name:"Server#2");
otherServer.backup(DataItem(type: DataItem.ItemType.Email, data: "bob@example.com")); globalLogger.log("Backed up 1 item to \(otherServer.name)");
globalLogger.printLog();
正如单例规则讲述的那样,全局变量使用let定义和lazy初始化可以保证在需要的时候才进行初始化,并且是线程安全的。这里使用final
修饰类名,是防止通过其子类的方式创建其他实例。
我们现在再次运行我们的程序,可以得到预期的结果(打印两个Log信息)
Log: Created new server Server#1
Log: Server#1 backed up item of type Email Address
Log: Server#1 backed up item of type Telephone Number
Log: Backed up 2 items to Server#1
Log: Created new server Server#2
Log: Server#2 backed up item of type Email Address
Log: Backed up 1 item to Server#2
传统方法实现单例
我们同样可以通过结构体来实现单例效果,在这里对Server实现单例,解决错误范例一的问题:
final class BackupServer {
let name:String;
private var data = [DataItem]();
private init(name:String) {
self.name = name;
globalLogger.log("Created new server \(name)");
}
func backup(item:DataItem) {
data.append(item);
globalLogger.log("\(name) backed up item of type \(item.type.rawValue)");
}
func getData() -> [DataItem]{
return data;
}
class var server:BackupServer {
struct SingletonWrapper {
static let singleton = BackupServer(name:"MainServer");
}
return SingletonWrapper.singleton;
}
}
修改main.Swift
var server = BackupServer.server;
server.backup(DataItem(type: DataItem.ItemType.Email, data: "joe@example.com"));
server.backup(DataItem(type: DataItem.ItemType.Phone, data: "555-123-1133"));
globalLogger.log("Backed up 2 items to \(server.name)");
var otherServer = BackupServer.server;
otherServer.backup(DataItem(type: DataItem.ItemType.Email, data: "bob@example.com"));
globalLogger.log("Backed up 1 item to \(otherServer.name)");
globalLogger.printLog();
server
和otherServer
本质上都是一个单例Server,所以这意味这所有的DataItem
都被发送到一个Server上。
单行单例法
前段时间看到一个有趣的博客“Swift中编写单例的正确方式”,里面阐述了一种有效并且简洁的方法--单行单例法
class TheSingleton {
static let sharedInstance = TheSingleton()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
我们来分析一下这个方法是不是可靠,首先阅读以下苹果官方文档
(“The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as
dispatch_once
to make sure that the initialization is atomic. This enables a cool way to usedispatch_once
in your code: just declare a global variable with an initializer and mark it private.”)
这个翻译出来也就是我们上面单例规则的第三条,可见并没有提到静态类变量是否可以满足单例的要求。作者在Swift中编写单例的正确方式帮我们证实了静态类变量的可行性。以后可以根据个人的爱好来选择单例模式的实现形式,非常完美。