在Swift中使用dispatch_once单例模型

我试图找出一个合适的单例模型在Swift中的使用。 到目前为止,我已经能够得到一个非线程安全的模型,其工作原理如下:

class var sharedInstance:TPScopeManager { get { struct Static { static var instance : TPScopeManager? = nil } if !Static.instance { Static.instance = TPScopeManager() } return Static.instance! } } 

在Static结构中包装单例实例应该允许单个实例不会与单例实例发生冲突,而没有复杂的命名规则,它应该使事情相当私密。 显然,这个模型并不是线程安全的,所以我试图添加dispatch_once到整个事情:

 class var sharedInstance:TPScopeManager { get { struct Static { static var instance : TPScopeManager? = nil static var token : dispatch_once_t = 0 } dispatch_once(Static.token) { Static.instance = TPScopeManager() } return Static.instance! } } 

但是我在dispatch_once行收到一个编译器错误:

无法将expression式的types'Void'转换为键入'()'

我已经尝试了几种不同的语法变体,但它们似乎都有相同的结果:

 dispatch_once(Static.token, { Static.instance = TPScopeManager() }) 

dispatch_once使用Swift的正确用法是什么? 我最初以为这个问题是由于错误消息中的()造成的,但是我越看越,我越觉得可能是正确定义dispatch_once_t的问题。

tl; dr:如果您使用Swift 1.2或更高版本,请使用类常量方法,如果需要支持早期版本,请使用嵌套结构方法。

根据我对Swift的经验,有三种方法来实现支持延迟初始化和线程安全的Singleton模式。

类常量

 class Singleton { static let sharedInstance = Singleton() } 

这种方法支持延迟初始化,因为Swift懒惰地初始化类常量(和variables),并且由let的定义是线程安全的。 这是现在正式推荐的方式来实例化一个单身人士。

类常量是在Swift 1.2中引入的。 如果您需要支持早期版本的Swift,请使用下面的嵌套结构方法或全局常量。

嵌套的结构

 class Singleton { class var sharedInstance: Singleton { struct Static { static let instance: Singleton = Singleton() } return Static.instance } } 

这里我们使用嵌套结构的静态常量作为类常量。 这是Swift 1.1和更早版本中缺less静态类常量的解决方法,并且仍然作为缺less函数中静态常量和variables的解决方法。

dispatch_once

传统的Objective-C方法移植到了Swift。 我相当肯定,嵌套的结构方法没有什么优势,但我把它放在这里,因为我发现语法的差异有趣。

 class Singleton { class var sharedInstance: Singleton { struct Static { static var onceToken: dispatch_once_t = 0 static var instance: Singleton? = nil } dispatch_once(&Static.onceToken) { Static.instance = Singleton() } return Static.instance! } } 

请参阅此GitHub项目进行unit testing。

由于苹果公司已经澄清,静态结构variables是初始化懒惰和包裹在dispatch_once(见post末尾的说明),我认为我的最终解决scheme将是:

 class WithSingleton { class var sharedInstance :WithSingleton { struct Singleton { static let instance = WithSingleton() } return Singleton.instance } } 

这利用了静态结构元素的自动惰性,线程安全的初始化,安全地隐藏了消费者的实际实现,保持了一切紧凑的易于区分,并消除了可见的全局variables。

苹果公司澄清说,懒惰初始化是线程安全的,所以不需要dispatch_once或类似的保护

全局variables的惰性初始值设定项(也是结构体和枚举的静态成员)在第一次访问全局时运行,并作为dispatch_once启动,以确保初始化是primefaces性的。 这使得在你的代码中使用dispatch_once很酷的方法:只要声明一个全局variables与初始值设定项并将其标记为私有。

从这里

对于Swift 1.2及更高版本:

 class Singleton { static let sharedInstance = Singleton() } 

有了正确的certificate(所有的功劳都在这里 ),现在几乎没有理由使用任何以前的单身人士的方法。

更新 :这是官方文档中描述的官方定义单身人士的方式!

至于使用static vs class 。 即使classvariables变为可用时, static应该是使用的。 单身并不意味着被分类,因为这会导致基础单身的多个实例。 使用static以一种美丽的Swifty方式强制执行此操作。

对于Swift 1.0和1.1:

随着Swift最近的变化,大多是新的访问控制方法,我现在倾向于使用一个全局variables的单身人士更清洁的方式。

 private let _singletonInstance = SingletonClass() class SingletonClass { class var sharedInstance: SingletonClass { return _singletonInstance } } 

正如Swift博客文章中提到的那样:

全局variables的惰性初始值设定项(也是结构体和枚举的静态成员)在第一次访问全局时运行,并以dispatch_once的forms启动,以确保初始化是primefaces性的。 这使得在你的代码中使用dispatch_once很酷的方法:只要声明一个全局variables与初始值设定项并将其标记为私有。

这种创build单例的方式是线程安全,快速,懒惰,并且也可以免费桥接到ObjC。

Swift 1.2或更高版本现在支持类中的静态variables/常量。 所以你可以使用一个静态常量:

 class MySingleton { static let sharedMySingleton = MySingleton() private init() { // ... } } 

有一个更好的方法来做到这一点。 你可以在类的声明之上声明一个全局variables,就像这样

 var tpScopeManagerSharedInstance = TPScopeManager() 

这只是调用你的默认init或Swift默认的init和全局variablesdispatch_once。 然后,无论你想获得参考,你只要做到这一点:

 var refrence = tpScopeManagerSharedInstance // or you can just access properties and call methods directly tpScopeManagerSharedInstance.someMethod() 

所以基本上你可以摆脱整个共享实例代码块。

Swift的单例在Cocoa框架中被暴露为类的函数,例如NSFileManager.defaultManager()NSNotificationCenter.defaultCenter() ,所以我觉得作为一个类函数来反映这种行为更有意义,而不是像其他一些解决scheme使用,例如

 class MyClass { private static let _sharedInstance = MyClass() class func sharedInstance() -> MyClass { return _sharedInstance } } 

通过MyClass.sharedInstance()获取单例。

根据苹果的文档 ,它已经重复了很多次,在Swift中最简单的方法是使用静态types属性:

 class Singleton { static let sharedInstance = Singleton() } 

但是,如果您正在寻找一种方法来执行超出简单构造函数调用的其他设置,秘密就是使用立即调用的闭包:

 class Singleton { static let sharedInstance: Singleton = { let instance = Singleton() // setup code return instance }() } 

这被保证是线程安全的,只能被初始化一次。

Swift 4+

 protocol Singleton: class { static var sharedInstance: Self { get } } final class Kraken: Singleton { static let sharedInstance = Kraken() private init() {} } 

看看苹果的示例代码,我遇到了这种模式。 我不确定Swift如何处理静态,但这将是C#中的线程安全。 我包括Objective-C interop的属性和方法。

 struct StaticRank { static let shared = RankMapping() } class func sharedInstance() -> RankMapping { return StaticRank.shared } class var shared:RankMapping { return StaticRank.shared } 

如果你打算在Objective-C中使用你的Swift单例类,这个设置将会让编译器生成合适的Objective-C头文件:

 class func sharedStore() -> ImageStore { struct Static { static let instance : ImageStore = ImageStore() } return Static.instance } 

然后在Objective-C类中,你可以像在Swift之前那样调用你的单例:

 [ImageStore sharedStore]; 

这只是我简单的实现。

首先解决scheme

 let SocketManager = SocketManagerSingleton(); class SocketManagerSingleton { } 

稍后在你的代码中:

 func someFunction() { var socketManager = SocketManager } 

第二种scheme

 func SocketManager() -> SocketManagerSingleton { return _SocketManager } let _SocketManager = SocketManagerSingleton(); class SocketManagerSingleton { } 

后来在你的代码中,你将能够保持较less混淆的括号:

 func someFunction() { var socketManager = SocketManager() } 

使用:

 class UtilSingleton: NSObject { var iVal: Int = 0 class var shareInstance: UtilSingleton { get { struct Static { static var instance: UtilSingleton? = nil static var token: dispatch_once_t = 0 } dispatch_once(&Static.token, { Static.instance = UtilSingleton() }) return Static.instance! } } } 

如何使用:

 UtilSingleton.shareInstance.iVal++ println("singleton new iVal = \(UtilSingleton.shareInstance.iVal)") 

简单来说,

 class Manager { static let sharedInstance = Manager() private init() {} } 

您可能想要阅读文件和初始化

全局variables的惰性初始值设定项(也是结构体和枚举的静态成员)在第一次访问全局时运行,并作为dispatch_once启动,以确保初始化是primefaces性的。

 final class MySingleton { private init() {} static let shared = MySingleton() } 

然后调用它;

 let shared = MySingleton.shared 

我会build议一个枚举,就像你会用在Java中,例如:

 enum SharedTPScopeManager: TPScopeManager { case Singleton } 

仅供参考,下面是Jack Wu / hpique的嵌套Struct实现的Singleton实现。 该实施还显示了归档如何工作,以及一些附带的function。 我无法find这个完整的例子,所以希望这可以帮助别人!

 import Foundation class ItemStore: NSObject { class var sharedStore : ItemStore { struct Singleton { // lazily initiated, thread-safe from "let" static let instance = ItemStore() } return Singleton.instance } var _privateItems = Item[]() // The allItems property can't be changed by other objects var allItems: Item[] { return _privateItems } init() { super.init() let path = itemArchivePath // Returns "nil" if there is no file at the path let unarchivedItems : AnyObject! = NSKeyedUnarchiver.unarchiveObjectWithFile(path) // If there were archived items saved, set _privateItems for the shared store equal to that if unarchivedItems { _privateItems = unarchivedItems as Array<Item> } delayOnMainQueueFor(numberOfSeconds: 0.1, action: { assert(self === ItemStore.sharedStore, "Only one instance of ItemStore allowed!") }) } func createItem() -> Item { let item = Item.randomItem() _privateItems.append(item) return item } func removeItem(item: Item) { for (index, element) in enumerate(_privateItems) { if element === item { _privateItems.removeAtIndex(index) // Delete an items image from the image store when the item is // getting deleted ImageStore.sharedStore.deleteImageForKey(item.itemKey) } } } func moveItemAtIndex(fromIndex: Int, toIndex: Int) { _privateItems.moveObjectAtIndex(fromIndex, toIndex: toIndex) } var itemArchivePath: String { // Create a filepath for archiving let documentDirectories = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) // Get the one document directory from that list let documentDirectory = documentDirectories[0] as String // append with the items.archive file name, then return return documentDirectory.stringByAppendingPathComponent("items.archive") } func saveChanges() -> Bool { let path = itemArchivePath // Return "true" on success return NSKeyedArchiver.archiveRootObject(_privateItems, toFile: path) } } 

如果你没有认识到这些function,下面是我用过的一个小小的Swift实用程序文件:

 import Foundation import UIKit typealias completionBlock = () -> () extension Array { func contains(#object:AnyObject) -> Bool { return self.bridgeToObjectiveC().containsObject(object) } func indexOf(#object:AnyObject) -> Int { return self.bridgeToObjectiveC().indexOfObject(object) } mutating func moveObjectAtIndex(fromIndex: Int, toIndex: Int) { if ((fromIndex == toIndex) || (fromIndex > self.count) || (toIndex > self.count)) { return } // Get object being moved so it can be re-inserted let object = self[fromIndex] // Remove object from array self.removeAtIndex(fromIndex) // Insert object in array at new location self.insert(object, atIndex: toIndex) } } func delayOnMainQueueFor(numberOfSeconds delay:Double, action closure:()->()) { dispatch_after( dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)) ), dispatch_get_main_queue()) { closure() } } 

我更喜欢这个实现:

 class APIClient { } var sharedAPIClient: APIClient = { return APIClient() }() extension APIClient { class func sharedClient() -> APIClient { return sharedAPIClient } } 

我在Swift中的实现方式

ConfigurationManager.swift

 import Foundation let ConfigurationManagerSharedInstance = ConfigurationManager() class ConfigurationManager : NSObject { var globalDic: NSMutableDictionary = NSMutableDictionary() class var sharedInstance:ConfigurationManager { return ConfigurationManagerSharedInstance } init() { super.init() println ("Config Init been Initiated, this will be called only onece irrespective of many calls") } 

从下面的应用程序的任何屏幕访问globalDic。

读:

  println(ConfigurationManager.sharedInstance.globalDic) 

写:

  ConfigurationManager.sharedInstance.globalDic = tmpDic // tmpDict is any value that to be shared among the application 

从Apple Docs (Swift 3.0.1),

您可以简单地使用静态types属性,即使在同时跨多个线程访问时,也只能保证只进行一次延迟初始化:

 class Singleton { static let sharedInstance = Singleton() } 

如果您需要在初始化之外执行额外的设置,则可以将调用闭包的结果分配给全局常量:

 class Singleton { static let sharedInstance: Singleton = { let instance = Singleton() // setup code return instance }() } 

在看到David的实现之后,似乎没有必要拥有一个singleton类的函数instanceMethod,因为let和sharedInstance类的方法几乎是一样的。 所有你需要做的就是将其声明为一个全局常量,就是这样。

 let gScopeManagerSharedInstance = ScopeManager() class ScopeManager { // No need for a class method to return the shared instance. Use the gScopeManagerSharedInstance directly. } 

在1.2以上的Swift中,最好的方法是单行单例,

 class Shared: NSObject { static let sharedInstance = Shared() private override init() { } } 

要了解更多关于这种方法的细节,你可以访问这个链接 。

  func init() -> ClassA { struct Static { static var onceToken : dispatch_once_t = 0 static var instance : ClassA? = nil } dispatch_once(&Static.onceToken) { Static.instance = ClassA() } return Static.instance! } 

我只是遇到了这个,但我要求我的单身人士允许inheritance,并没有任何这些解决scheme实际上允许它。

所以我想出了这个:

 public class Singleton { private static var sharedInstanceVar = Singleton() public class func sharedInstance()->Singleton { return sharedInstanceVar } } public class SubSingleton: Singleton { private static var sharedInstanceToken:dispatch_once_t = 0 public class override func sharedInstance()->SubSingleton { dispatch_once(&sharedInstanceToken){ sharedInstanceVar = SubSingleton() } return sharedInstanceVar as! SubSingleton } } 
  • 这样做时,Singleton.sharedInstance()首先会返回Singleton的实例
  • 当首先执行SubSingleton.sharedInstance()时,它将返回创build的SubSingleton的实例。
  • 如果完成上述操作,则SubSingleton.sharedInstance()是Singleton是true,并使用相同的实例。

第一个脏方法的问题是,我不能保证子类将实现dispatch_once_t,并确保每个类只修改一次sharedInstanceVar …

我会尽量进一步改进,但是看看是否有人对此有强烈的感觉(除了冗长的事实,并且需要手动更新),这将是有趣的。

这是线程安全function中最简单的一个。 即使他们想要,其他线程也不能访问同一个单例对象。 Swift 3/4

 struct DataService { private static var _instance : DataService? private init() {} //cannot initialise from outer class public static var instance : DataService { get { if _instance == nil { DispatchQueue.global().sync(flags: .barrier) { if _instance == nil { _instance = DataService() } } } return _instance! } } } 

这是我的实现。 它也阻止程序员创build一个新的实例:

 let TEST = Test() class Test { private init() { // This is a private (!) constructor } } 
 private var sharedURLCacheForRequestsKey:Void? extension URLCache{ public static func sharedURLCacheForRequests()->URLCache{ var cache = objc_getAssociatedObject(OperationQueue.main, &sharedURLCacheForRequestsKey) if cache is URLCache { }else{ cache = URLCache(memoryCapacity: 0, diskCapacity: 1*1024*1024*1024, diskPath: "sharedURLCacheForRequestsKey") objc_setAssociatedObject(OperationQueue.main, &sharedURLCacheForRequestsKey, cache, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } return cache as! URLCache }} 

我倾向于使用以下语法作为最完整的:

 public final class Singleton { private class func sharedInstance() -> Singleton { struct Static { //Singleton instance. static let sharedInstance = Singleton() } return Static.sharedInstance } private init() { } class var instance: Singleton { return sharedInstance() } } 

这从Swift1.2到4可以工作,并提供了几个优点:

  1. 提醒用户不要inheritance实现
  2. 防止创build其他实例
  3. 确保延迟创build和独特的实例化
  4. 通过允许以Singleton.instance身份访问实例来缩短语法(避免())