Design Patterns in Swift:单例模式(上)

转自: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信息,完全没有必要创建多个对象来进行记录,那么下面使用单例模式来重新完善程序。

单例规则

  1. 单例必须在程序生命周期中是唯一的,例如:NSNotificationCenterUIApplicationNSUserDefaults
  2. 为了保证单例是唯一的,初始化方法必须是私有的。
  3. 单例必须是线程安全的。在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();

serverotherServer本质上都是一个单例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 use dispatch_once in your code: just declare a global variable with an initializer and mark it private.”)

这个翻译出来也就是我们上面单例规则的第三条,可见并没有提到静态类变量是否可以满足单例的要求。作者在Swift中编写单例的正确方式帮我们证实了静态类变量的可行性。以后可以根据个人的爱好来选择单例模式的实现形式,非常完美。

参考文章:
Swift中编写单例的正确方式Design Patterns in Swift

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值