在Swift中使用dispatch_once单例模型

Dav*_*rry 563 singleton dispatch swift

我正在尝试找出适合在Swift中使用的单例模型.到目前为止,我已经能够得到一个非线程安全模型:

class var sharedInstance: TPScopeManager {
    get {
        struct Static {
            static var instance: TPScopeManager? = nil
        }

        if !Static.instance {
            Static.instance = TPScopeManager()
        }

        return Static.instance!
    }
}
Run Code Online (Sandbox Code Playgroud)

在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!
    }
}
Run Code Online (Sandbox Code Playgroud)

但我得到一个编译器错误dispatch_once:

无法将表达式的类型'Void'转换为'()'类型

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

dispatch_once(Static.token, { Static.instance = TPScopeManager() })
Run Code Online (Sandbox Code Playgroud)

dispatch_once使用Swift 的正确用法是什么?我最初认为问题出在块中,因为dispatch_once错误消息,但我看的越多,我认为可能是获得()正确定义的问题.

hpi*_*que 705

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

根据我使用Swift的经验,有三种方法可以实现支持延迟初始化和线程安全的Singleton模式.

类常数

class Singleton  {
   static let sharedInstance = Singleton()
}
Run Code Online (Sandbox Code Playgroud)

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

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

嵌套结构

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}
Run Code Online (Sandbox Code Playgroud)

这里我们使用嵌套结构的静态常量作为类常量.这是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!
    }
}
Run Code Online (Sandbox Code Playgroud)

请参阅此GitHub项目以进行单元测试.

  • "凭借让"线程安全" - 这已在任何地方陈述过吗?我在文档中找不到它. (12认同)
  • `init`也应该被声明为`private`以保证在整个应用程序的生命周期中只存在一个对象实例? (5认同)
  • @jtbandes常量是我所知道的所有语言的线程安全. (4认同)
  • 在"类常量"方法中,我建议(a)将类声明为`final`,这样你就不会对它进行子类化; (b)将`init`方法声明为`private`,这样你就不会在某处意外地实例化另一个实例. (4认同)
  • @DaveWood我假设你在谈论最后一种方法.我会引用自己的话:"我会说不再需要使用这种方法,但无论如何我都把它放在这里,因为我发现语法上的差异很有趣." (2认同)
  • 苹果已经记录了“类常量”方法:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7- ID177 (2认同)

Dav*_*rry 173

由于Apple现在澄清了静态结构变量的初始化既懒又包裹在dispatch_once中(参见帖子末尾的注释),我认为我的最终解决方案将是:

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

        return Singleton.instance
    }
}
Run Code Online (Sandbox Code Playgroud)

这利用了静态结构元素的自动延迟,线程安全初始化,安全地隐藏了消费者的实际实现,保持所有紧凑区分以便易读,并消除了可见的全局变量.

Apple已澄清懒惰初始化程序是线程安全的,因此不需要dispatch_once或类似的保护

全局变量(也适用于结构和枚举的静态成员)的惰性初始化程序在第一次访问全局时运行,并作为dispatch_once启动,以确保初始化是原子的.这样就可以在代码中使用dispatch_once:只需使用初始化程序声明一个全局变量并将其标记为私有.

这里开始

  • 我想补充一点,一个好的做法是将初始化程序声明为private:`private init(){}`,以进一步强制实现这个类不打算在外部实例化. (13认同)

Jac*_*ack 163

对于Swift 1.2及更高版本:

class Singleton  {
   static let sharedInstance = Singleton()
}
Run Code Online (Sandbox Code Playgroud)

有了正确性证明(所有信用都在这里),现在几乎没有理由使用任何以前的单身人士方法.

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

至于使用staticvs的担忧class.static即使class变量可用,也应该使用它.单例并不意味着被子类化,因为这将导致基本单例的多个实例.使用static以美丽,Swifty的方式强制执行此操作.

对于Swift 1.0和1.1:

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

private let _singletonInstance = SingletonClass()
class SingletonClass {
  class var sharedInstance: SingletonClass {
    return _singletonInstance
  }
}
Run Code Online (Sandbox Code Playgroud)

随着斯威夫特博客文章中提到在这里:

全局变量(也适用于结构和枚举的静态成员)的惰性初始化程序在第一次访问全局时运行,并作为dispatch_once启动,以确保初始化是原子的.这样就可以在代码中使用dispatch_once:只需使用初始化程序声明一个全局变量并将其标记为私有.

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

  • 您可能希望添加`private init(){}`作为`SingletonClass`的初始化.防止从外部实例化. (6认同)
  • 只读这个答案的人:记住要使令牌静态,否则行为是未定义的.有关完整代码,请参阅David编辑过的问题. (2认同)

Flo*_*ian 46

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

class MySingleton {

    static let sharedMySingleton = MySingleton()

    private init() {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)


Kri*_*lci 34

有一种更好的方法.你可以在类的decleration之上声明一个全局变量,就像这样

var tpScopeManagerSharedInstance = TPScopeManager()
Run Code Online (Sandbox Code Playgroud)

这只是在Swift中调用默认初始化或默认的init和全局变量dispatch_once.然后在您想要获得参考的任何课程中,您只需执行以下操作:

var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么"var"和很多"让"? (3认同)

Rya*_*yan 27

Swift单例在Cocoa框架中作为类函数公开,例如NSFileManager.defaultManager(),NSNotificationCenter.defaultCenter()所以我觉得作为镜像这种行为的类函数更有意义,而不是像其他一些解决方案那样使用的类变量,例如

class MyClass {

    private static let _sharedInstance = MyClass()

    class func sharedInstance() -> MyClass {
        return _sharedInstance
    }
}
Run Code Online (Sandbox Code Playgroud)

检索单身人士MyClass.sharedInstance().

  • 全局变量应该通过类内部的静态更改为类变量。 (2认同)
  • @malhal 当一个变量被标记为私有但在类之外时,它不是全局的 - 但仅限于它所在的文件。类内的静态几乎相同,但我已经更新了使用静态的答案正如您所建议的,如果您碰巧在文件中使用多个类,它会更好地将变量分组到类中。 (2认同)

Adr*_*eil 16

根据Apple文档,它已多次重复,在Swift中执行此操作的最简单方法是使用静态类型属性:

class Singleton {
    static let sharedInstance = Singleton()
}
Run Code Online (Sandbox Code Playgroud)

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

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}
Run Code Online (Sandbox Code Playgroud)

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


Ada*_*aka 16

Swift 4+

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

final class Kraken: Singleton {
    static let sharedInstance = Kraken()
    private init() {}
}
Run Code Online (Sandbox Code Playgroud)

  • 这需要最终的类,你能解释更多的差异,因为我对单例与struct的其他解决方案有问题 (2认同)

小智 8

看看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
}
Run Code Online (Sandbox Code Playgroud)


onm*_*133 5

简单来说,

class Manager {
    static let sharedInstance = Manager()
    private init() {}
}
Run Code Online (Sandbox Code Playgroud)

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

全局变量的延迟初始化程序(也适用于结构和枚举的静态成员)在第一次访问全局时运行,并且启动dispatch_once以确保初始化是原子的.