在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行上得到一个编译器错误:

不能将表达式的类型“Void”转换为类型“()”

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

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

dispatch_once使用Swift的正确用法是什么?我最初认为问题是由于错误消息中的()导致的块,但我看得越多,我越认为这可能是正确定义dispatch_once_t的问题。

136183 次浏览

对于Swift 1.2及以上版本:

class Singleton  {
static let sharedInstance = Singleton()
}

有了正确的证明(所有功劳都归在这里),现在几乎没有理由为单例对象使用任何前面的方法了。

更新:这现在是官方定义单例的方法,正如官方文档!

至于使用static vs class的问题。即使class变量可用,也应该使用static。单例不应该被子类化,因为那样会导致基单例的多个实例。使用static可以以一种漂亮的、Swifty的方式实现这一点。

对于Swift 1.0和1.1:

随着Swift最近的变化,主要是新的访问控制方法,我现在倾向于对单例对象使用全局变量的更清洁的方式。

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

正如Swift博客文章在这里中提到的:

的静态成员的惰性初始化式 Structs和enum)在第一次访问global时运行,并且 作为dispatch_once启动,以确保初始化为 原子。这为在代码中使用dispatch_once提供了一种很酷的方式: 只需声明一个带有初始化式的全局变量并标记它 私人的。< / p >

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

在查看Apple的示例代码时,我发现了这种模式。我不确定Swift是如何处理静态数据的,但这在c#中是线程安全的。我包括了Objective-C互操作的属性和方法。

struct StaticRank {
static let shared = RankMapping()
}


class func sharedInstance() -> RankMapping {
return StaticRank.shared
}


class var shared:RankMapping {
return StaticRank.shared
}

有更好的办法。你可以像这样在你的类声明上面声明一个全局变量:

var tpScopeManagerSharedInstance = TPScopeManager()

这只是调用你的默认init或任何init和全局变量在Swift默认为dispatch_once。然后在任何你想要引用的类中,你只需要这样做:

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

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

由于苹果现在已经澄清了静态结构变量是惰性初始化和包装在dispatch_once中(见文章末尾的说明),我认为我的最终解决方案将是:

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


return Singleton.instance
}
}

这利用了静态结构元素的自动惰性、线程安全初始化,对使用者安全地隐藏了实际的实现,保持所有内容紧凑地分隔以使其易于阅读,并消除了可见的全局变量。

苹果已经澄清了惰性初始化器是线程安全的,所以不需要dispatch_once或类似的保护

全局变量(也包括struct和enum的静态成员)的惰性初始化器在第一次访问global时运行,并作为dispatch_once启动,以确保初始化是原子的。这为在代码中使用dispatch_once提供了一种很酷的方式:只需声明一个带有初始化式的全局变量并将其标记为private。

从# EYZ0

使用:

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)")

在看到David的实现之后,似乎没有必要使用单个类函数instanceMethod,因为let所做的事情与sharedInstance类方法几乎相同。你所要做的就是把它声明为一个全局常数,就是这样。

let gScopeManagerSharedInstance = ScopeManager()


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

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

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

类常量

class Singleton  {
static let sharedInstance = Singleton()
}

这种方法支持延迟初始化,因为Swift会延迟初始化类常量(和变量),并且通过let的定义是线程安全的。现在是官方推荐方式来实例化一个单例。

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

嵌套的结构体

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

这里我们使用嵌套结构的静态常量作为类常量。这是针对Swift 1.1及更早版本中缺少静态类常量的一种变通方法,并且仍然可以作为函数中缺少静态常量和变量的一种变通方法。

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项目。

我更喜欢这样的实现:

class APIClient {


}


var sharedAPIClient: APIClient = {
return APIClient()
}()


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

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

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

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

[ImageStore sharedStore];

这只是我的简单实现。

我建议使用enum,就像您在Java中使用的那样,例如。

enum SharedTPScopeManager: TPScopeManager {
case Singleton
}

这里有一个Jack Wu/hpique的嵌套结构实现的单例实现示例,仅供参考。实现还展示了如何进行归档,以及一些附带的功能。我找不到这么完整的例子,希望这能对大家有所帮助!

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

如果你不认识其中的一些函数,这里有一个我一直在使用的小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()
}
}

我的实现方式在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

第一个解决方案

let SocketManager = SocketManagerSingleton();


class SocketManagerSingleton {


}

在后面的代码中:

func someFunction() {
var socketManager = SocketManager
}

第二个解决方案

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


class SocketManagerSingleton {


}

在后面的代码中,你可以使用大括号来减少混淆:

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

Swift单例在Cocoa框架中作为类函数公开,例如NSFileManager.defaultManager()NSNotificationCenter.defaultCenter()。因此,作为一个类函数来反映这种行为比作为其他解决方案的类变量更有意义。例句:

class MyClass {


private static let _sharedInstance = MyClass()


class func sharedInstance() -> MyClass {
return _sharedInstance
}
}

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

   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!
}

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

class MySingleton {


static let sharedMySingleton = MySingleton()


private init() {
// ...
}
}

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

class Singleton {
static let sharedInstance = Singleton()
}

然而,如果你正在寻找一种方法来执行一个简单的构造函数调用之外的额外设置,秘密是使用一个立即调用的闭包:

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

这保证是线程安全的,并且只进行一次惰性初始化。

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

那就叫它;

let shared = MySingleton.shared

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

let TEST = Test()


class Test {


private init() {
// This is a private (!) constructor
}
}

简而言之,

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

你可能想读文件和初始化

的静态成员的惰性初始化式 Structs和enum)在第一次访问global时运行,并且 启动为dispatch_once,以确保初始化是 原子。< / p >

我要求我的单例允许继承,而这些解决方案实际上都不允许。所以我想到了这个:

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()时,它将返回已创建的SubSingleton实例。
  • 如果执行了上述操作,则SubSingleton.sharedInstance()为真,Singleton为真,并且使用相同的实例。

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

我将尝试进一步完善它,但是看看是否有人对此有强烈的反对(除了它很冗长并且需要手动更新之外),这将是很有趣的。

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

class Shared: NSObject {


static let sharedInstance = Shared()


private override init() { }
}

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

斯威夫特4 +

protocol Singleton: class {
static var sharedInstance: Self { get }
}


final class Kraken: Singleton {
static let sharedInstance = Kraken()
private init() {}
}

从Apple Docs (Swift 3.0.1),

您可以简单地使用静态类型属性,这是保证 惰性初始化仅一次,即使跨多个访问 线程同时:< / p >
class Singleton {
static let sharedInstance = Singleton()
}

如果您需要执行初始化之外的其他设置,您可以 将闭包调用的结果赋值给全局变量 常数:< / p >

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

这是具有线程安全功能的最简单的一个。即使其他线程想要访问同一个单例对象,它们也不能访问。# EYZ0

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!
}
}
}

我使用以下语法:

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

从Swift 1.2到Swift 4都可以使用,有几个优点:

  1. 提醒用户不要子类化实现
  2. 防止创建额外的实例
  3. 确保惰性创建和唯一实例化
  4. 通过允许以Singleton.instance访问实例来缩短语法(avoid ())

Swift过去实现单例,无非是三种方式:全局变量、内部变量和dispatch_once方式。

这里有两个不错的单品。(注:无论哪种写法都一定要注意init()私有化方法。因为在Swift中,所有对象的构造函数default都是public,需要重写init就可以变成private,防止该类的其他对象通过默认的初始化方法来创建对象。)

方法1:

class AppManager {
private static let _sharedInstance = AppManager()


class func getSharedInstance() -> AppManager {
return _sharedInstance
}


private init() {} // Privatizing the init method
}


// How to use?
AppManager.getSharedInstance()

方法2:

class AppManager {
static let sharedInstance = AppManager()


private init() {} // Privatizing the init method
}


// How to use?
AppManager.sharedInstance

唯一正确的方法如下。

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


private init() {}
}

访问

let signleton = Singleton.sharedInstance

原因:

  • static类型的属性被保证只被惰性初始化一次,即使在多线程同时访问时也是如此,所以不需要使用dispatch_once
  • 私有化init方法,因此实例不能由其他类创建。
  • final类,因为你不希望其他类继承单例类。

在swift中,你可以通过以下方式创建一个单例类:

class AppSingleton: NSObject {


//Shared instance of class
static let sharedInstance = AppSingleton()


override init() {
super.init()
}
}

斯威夫特5.2

您可以使用Self指向类型。所以:

static let shared = Self()

并且应该在一个类型中,比如:

class SomeTypeWithASingletonInstance {
static let shared = Self()
}