首页 文章

在Swift中使用dispatch_once单例模型

提问于
浏览
542

我正在尝试找出适合在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() })

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

30 回答

  • 3

    在看到David 's implementation, it seems like there'之后,不需要单例类函数instanceMethod,因为 let 与sharedInstance类方法几乎完全相同 . 你需要做的就是将它声明为一个全局常量,就是这样 .

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

    Swift 1.2以上的最佳方法是单行单例,如 -

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

    要了解有关此方法的更多详细信息,请访问此link .

  • -1

    唯一正确的方法如下

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

    访问

    let signleton = Singleton.sharedInstance
    

    Reasons:

    • 静态类型属性保证只延迟初始化一次,即使同时跨多个线程访问,也不需要使用dispatch_once

    • 私有化init方法,因此其他类不能创建实例 .

    • final类,因为您不希望其他类继承Singleton类

  • 1

    这是最简单的具有线程安全功能的 . 没有其他线程可以访问相同的单例对象,即使他们想要 . 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!
            }
        }
    }
    
  • 4

    我建议使用Enum,就像你在Java中使用的那样,例如:

    enum SharedTPScopeManager: TPScopeManager {
      case Singleton
    }
    
  • 32

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

    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到4,并提供了几个优点:

    • 提醒用户不要实现子类

    • 防止创建其他实例

    • 确保延迟创建和独特的实例化

    • 通过允许访问实例_115645_缩短语法(avoidids())

  • 2

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

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

    通过 MyClass.sharedInstance() 检索单例 .

  • 5

    使用静态变量和私有初始值设定项来创建单例类 .

    class MySingletonClass {
    
        static let sharedSingleton = MySingletonClass()
    
        private init() {}
    }
    
  • 16

    For Swift 1.2 and beyond:

    class Singleton  {
       static let sharedInstance = Singleton()
    }
    

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

    Update :现在这是 official 定义单例的方法,如_135581中所述!

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

    For Swift 1.0 and 1.1:

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

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

    如Swift博客文章_135589中所述:

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

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

  • 677

    使用:

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

    看看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
    }
    
  • 2

    简单来说,

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

    你可能想阅读Files and Initialization

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

  • 160

    我更喜欢这个实现:

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

    Swift在过去实现单例,只不过是三种方式:全局变量,内部变量和dispatch_once方式 .

    这里有两个很好的单身人士 . (注意:无论何种写作都必须注意私有化的init()方法 . 因为在Swift中,所有对象的构造函数默认都是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
    
  • -1

    tl; dr:如果您使用的是Swift 1.2或更高版本,请使用 class constant 方法;如果您需要支持早期版本,请使用 nested struct 方法 .

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

    类常量

    class Singleton  {
       static let sharedInstance = Singleton()
    }
    

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

    类常量在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项目 .

  • 15

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

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

    我在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
    
  • 45

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

    class Singleton {
        static let sharedInstance = Singleton()
    }
    

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

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

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

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

    然后打电话给它;

    let shared = MySingleton.shared
    
  • -1

    这是我的实施 . 它还可以防止程序员创建新实例:

    let TEST = Test()
    
    class Test {
    
        private init() {
            // This is a private (!) constructor
        }
    }
    
  • 27

    Swift 4+

    protocol Singleton: class {
        static var sharedInstance: Self { get }
    }
    
    final class Kraken: Singleton {
        static let sharedInstance = Kraken()
        private init() {}
    }
    
  • 9
    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
    }}
    
  • 5

    如果您计划在Objective-C中使用Swift单例类,则此设置将使编译器生成适当的类似Objective-C的标头:

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

    然后在Objective-C课程中,你可以像在Swift之前的日子里那样调用你的单身人士:

    [ImageStore sharedStore];
    

    这只是我简单的实现 .

  • 5

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

    var tpScopeManagerSharedInstance = TPScopeManager()
    

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

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

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

  • 3
    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!
    }
    
  • 2

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

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

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

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

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

    here

  • 0

    我刚刚遇到过这个,但是我需要我的单例来允许继承,而这些解决方案都没有实际允许它 .

    所以我想出了这个:

    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为true且使用相同的实例 .

    第一个脏方法的问题是我无法保证子类将实现dispatch_once_t并确保每个类仅修改一次sharedInstanceVar ...

    我将尝试进一步完善这一点,但是看看是否有人对此有强烈的感情(除此之外)会很有趣这是一个冗长的事实,需要手动更新它 .

  • -1

    From Apple Docs (Swift 3.0.1),

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

    class Singleton {
        static let sharedInstance = Singleton()
    }
    

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

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

    First solution

    let SocketManager = SocketManagerSingleton();
    
    class SocketManagerSingleton {
    
    }
    

    稍后在您的代码中:

    func someFunction() {        
        var socketManager = SocketManager        
    }
    

    Second solution

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

    稍后在您的代码中,您将能够保持大括号以减少混淆:

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

    仅供参考,以下是Jack Wu / hpique的嵌套结构实现的Singleton实现示例 . 该实现还显示了归档如何工作,以及一些附带的功能 . 我找不到这个完整的例子,所以希望这有助于某人!

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

相关问题