在Swift中使用dispatch_once单例模型

时间:2014-06-03 20:41:12

标签: swift singleton dispatch

我试图找出适合在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

32 个答案:

答案 0 :(得分:695)

tl; dr:如果您使用的是Swift 1.2或更高版本,则使用类常量方法,如果需要支持早期版本,则使用嵌套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项目进行单元测试。

答案 1 :(得分:173)

由于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

答案 2 :(得分:162)

对于Swift 1.2及更高版本:

class Singleton  {
   static let sharedInstance = Singleton()
}

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

更新:现在这是官方方式来定义单身人士,如official docs所述!

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

对于Swift 1.0和1.1:

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

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

如Swift博客文章here中所述:

  

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

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

答案 3 :(得分:46)

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

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

答案 4 :(得分:33)

有一种更好的方法。你可以在你的类中声明一个全局变量,如上所述

var tpScopeManagerSharedInstance = TPScopeManager()

这只是调用你的默认初始化,或者默认情况下swift中的init和全局变量是dispatch_once。然后,无论您想要哪个课程参考,您都可以这样做:

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

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

答案 5 :(得分:27)

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

class MyClass {

    private static let _sharedInstance = MyClass()

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

通过MyClass.sharedInstance()检索单身人士。

答案 6 :(得分:16)

Swift 4 +

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

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

答案 7 :(得分:16)

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

--no-source

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

class Singleton {
    static let sharedInstance = Singleton()
}

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

答案 8 :(得分: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
}

答案 9 :(得分:5)

简而言之,

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

您可能需要阅读Files and Initialization

  

全局变量的惰性初始值设定项(也适用于静态成员)   第一次访问全局时运行结构和枚举)   以dispatch_once启动,以确保初始化   原子。

答案 10 :(得分:4)

第一个解决方案

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

答案 11 :(得分:4)

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

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

然后在Objective-C课程中,你可以按照在Swift之前的日子里的方式调用你的单身人士:

[ImageStore sharedStore];

这只是我的简单实现。

答案 12 :(得分:4)

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

然后打电话给它;

let shared = MySingleton.shared

答案 13 :(得分:4)

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

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

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

答案 14 :(得分:4)

使用:

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

答案 15 :(得分:3)

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

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

答案 16 :(得分:3)

来自Apple Docs(Swift 3.0.1),

  

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

import UIKit
import AVFoundation

class PlayerViewController: UIViewController {
    // This time, we'll declare avPlayer as an instance variable,
    // which means it exists as long as our view controller exists.
    var avPlayer: AVPlayer!

    override func viewDidLoad() {
        super.viewDidLoad()

        print("Connecting...")

        // If you'd rather not use this sound file,
        // replace the string below with the URL of some other MP3.
        let urlString = "http://live.canstream.co.uk:8000/bangradio.mp3"
        let url = NSURL(string: urlString)!
        avPlayer = AVPlayer(URL: url)

        print("Connected to One Radio!")
        avPlayer.play()
        print("Now playing...")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
  

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

class Singleton {
    static let sharedInstance = Singleton()
}

答案 17 :(得分: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()
    }
}

答案 18 :(得分:2)

下面是唯一正确的方法

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

    private init() {}
}

访问

let signleton = Singleton.sharedInstance

原因:

    保证
  • 静态类型属性仅被延迟初始化一次,即使同时访问多个线程也是如此,因此无需使用dispatch_once
  • 私有化init方法,因此其他类无法创建实例。
  • 最终类,因为您不希望其他类继承Singleton类

答案 19 :(得分:1)

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

答案 20 :(得分:1)

我更喜欢这种实现:

class APIClient {

}

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

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

答案 21 :(得分:1)

在看到David的实现之后,似乎不需要单例类函数instanceMethod,因为let与sharedInstance类方法完全相同。你需要做的就是将它声明为一个全局常量,就是这样。

let gScopeManagerSharedInstance = ScopeManager()

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

答案 22 :(得分:1)

您可以按照以下方式快速创建一个单例类:

class AppSingleton: NSObject {

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

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

答案 23 :(得分:0)

Swift 5.2

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

static let shared = Self()

并且应该在类型内部,例如:

class SomeTypeWithASingletonInstance {
   static let shared = Self()
}

答案 24 :(得分:0)

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

这里有两个好的单例。(注意:无论何种写作都必须注意私有化的init()方法。因为在Swift中,所有对象的构造函数默认都是public,需要是重写的init可以变成私有的,防止这个类的其他对象&#39;()&#39;默认初始化方法来创建对象。)

方法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

答案 25 :(得分:0)

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

答案 26 :(得分:-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!
        }
    }
}

答案 27 :(得分:-1)

使用静态变量和私有初始化程序创建一个单例类。

class MySingletonClass {

    static let sharedSingleton = MySingletonClass()

    private init() {}
}

答案 28 :(得分:-1)

我刚刚遇到过这个问题,但我要求我的单例允许继承,而这些解决方案实际上都没有允许继承。

所以我想出了这个:

import {...} from 'angular2/angular2'
  • 这种方式首先执行Singleton.sharedInstance()时会返回Singleton实例
  • 首先执行SubSingleton.sharedInstance()时,它将返回创建的SubSingleton实例。
  • 如果完成上述操作,则SubSingleton.sharedInstance()为Singleton为true且使用相同的实例。

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

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

答案 29 :(得分:-2)

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

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

  1. 提醒用户不要实现子类化
  2. 防止创建其他实例
  3. 确保延迟创建和独特实例化
  4. 允许以Singleton.instance
  5. 访问实例,缩短语法(avoidids())

答案 30 :(得分:-2)

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

答案 31 :(得分:-2)

这是我的实施。它还会阻止程序员创建新实例:

let TEST = Test()

class Test {

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