使用swift中的一次调度单例singleton模型

Using a dispatch_once singleton model in Swift

我正试图为Swift中的用法建立一个合适的单例模型。到目前为止,我已经能够得到一个非线程安全模型,其工作方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class var sharedInstance:TPScopeManager {
    get {
        struct Static {
            static var instance : TPScopeManager? = nil
        }

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

        return Static.instance!
    }
}

在静态结构中包装singleton实例应该允许一个不与singleton实例发生冲突的实例,而不需要复杂的命名计划,并且它应该使事情变得相当私有。显然,这个模型不是线程安全的,所以我尝试在整个过程中添加分派:

1
2
3
4
5
6
7
8
9
10
11
12
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行上得到一个编译器错误:

Cannot convert the expression's type 'Void' to type '()'

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

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

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


tl;dr:如果您使用的是swift 1.2或更高版本,请使用class-constant方法;如果您需要支持早期版本,请使用嵌套结构方法。

根据我对swift的经验,有三种方法可以实现支持延迟初始化和线程安全的单例模式。

类常数

1
2
3
class Singleton  {
   static let sharedInstance = Singleton()
}

这种方法支持延迟初始化,因为swift会延迟初始化类常量(和变量),并且根据EDOCX1的定义(6)是线程安全的。现在这是正式推荐的实例化单例的方法。

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

嵌套结构

1
2
3
4
5
6
7
8
class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

这里我们使用嵌套结构的静态常量作为类常量。这是一个解决Swift 1.1和更早版本中缺少静态类常量的方法,但仍然可以解决函数中缺少静态常量和变量的问题。

调度一次

传统的客观C方法向SWIFT发展。我相当肯定,与嵌套结构方法相比没有优势,但是我还是把它放在这里,因为我发现语法上的差异很有趣。

1
2
3
4
5
6
7
8
9
10
11
12
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项目。


由于苹果公司现在已经澄清了静态结构变量被初始化为lazy,并且被包装在dispatch_中一次(见文章末尾的注释),我认为我的最终解决方案是:

1
2
3
4
5
6
7
8
9
class WithSingleton {
    class var sharedInstance :WithSingleton {
        struct Singleton {
            static let instance = WithSingleton()
        }

        return Singleton.instance
    }
}

这利用了静态结构元素的自动延迟、线程安全初始化,安全地从使用者那里隐藏了实际的实现,使所有内容紧凑地划分以便于易读,并消除了可见的全局变量。

苹果已经澄清了懒惰的初始值设定项是线程安全的,因此不需要dispatch_once或类似的保护。

The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private.

从这里


对于Swift 1.2及更高版本:

1
2
3
class Singleton  {
   static let sharedInstance = Singleton()
}

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

更新:这是官方文档中描述的定义单例的官方方法!

关于使用staticclass的问题。即使当class变量可用时,也应该使用static。单例不应该被子类化,因为这样会导致基单例的多个实例。使用EDOCX1[0]可以以一种漂亮、快捷的方式强制执行此操作。

对于Swift 1.0和1.1:

随着swift(主要是新的访问控制方法)的最新变化,我现在倾向于使用全局变量进行单例访问的更清洁的方法。

1
2
3
4
5
6
private let _singletonInstance = SingletonClass()
class SingletonClass {
  class var sharedInstance: SingletonClass {
    return _singletonInstance
  }
}

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

The lazy initializer for a global variable (also for static members of
structs and enums) is run the first time that global is accessed, and
is launched as dispatch_once to make sure that the initialization is
atomic. This enables a cool way to use dispatch_once in your code:
just declare a global variable with an initializer and mark it
private.

这种创建singleton的方法是线程安全、快速、懒惰的,而且还免费桥接到objc。


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

1
2
3
4
5
6
7
8
class MySingleton {

    static let sharedMySingleton = MySingleton()

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


有更好的方法。您可以在类中的类declaration上面声明一个全局变量,就像这样

1
var tpScopeManagerSharedInstance = TPScopeManager()

这只调用您的默认init或默认的init和全局变量在swift中调度一次。然后在任何一个类中,只要你想得到一个引用,你就可以这样做:

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

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


在cocoa框架中,swift单件作为类函数公开,例如NSFileManager.defaultManager()NSNotificationCenter.defaultCenter(),因此我觉得作为类函数来反映这种行为更为合理,而不是像其他一些解决方案所使用的类变量,例如。

1
2
3
4
5
6
7
8
class MyClass {

    private static let _sharedInstance = MyClass()

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

通过MyClass.sharedInstance()检索单件。


根据Apple文档,在Swift中,最简单的方法是使用静态类型属性:

1
2
3
class Singleton {
    static let sharedInstance = Singleton()
}

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

1
2
3
4
5
6
7
class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

这保证了线程安全,并且只延迟初始化一次。


斯威夫特4 +

1
2
3
4
5
6
7
8
protocol Singleton: class {
    static var sharedInstance: Self { get }
}

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


看着苹果的示例代码,我发现了这个模式。我不确定Swift如何处理静态,但这在C中是线程安全的。我包含了Objective-C互操作的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}


简而言之,

1
2
3
4
class Manager {
    static let sharedInstance = Manager()
    private init() {}
}

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

The lazy initializer for a global variable (also for static members of
structs and enums) is run the first time that global is accessed, and
is launched as dispatch_once to make sure that the initialization is
atomic.


第一解

1
2
3
4
5
let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

代码后面部分:

1
2
3
func someFunction() {        
    var socketManager = SocketManager        
}

二解

1
2
3
4
5
6
7
8
func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

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

1
2
3
func someFunction() {        
    var socketManager = SocketManager()        
}

在1.2以上的swift中,最好的方法是单线单件,如-

1
2
3
4
5
6
class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

要了解有关此方法的更多详细信息,可以访问此链接。


用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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!
        }
    }
}

如何使用:

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


如果计划在Objective-C中使用Swift Singleton类,则此设置将使编译器生成适当的Objective-C类头文件:

1
2
3
4
5
6
class func sharedStore() -> ImageStore {
struct Static {
    static let instance : ImageStore = ImageStore()
    }
    return Static.instance
}

然后,在客观C类课程中,你可以像雨燕前那样称呼单身汉:

1
[ImageStore sharedStore];

这只是我的简单实现。


1
2
3
4
final class MySingleton {
     private init() {}
     static let shared = MySingleton()
}

然后叫它;

1
let shared = MySingleton.shared


我建议你在爪哇使用EnUM,例如:

1
2
3
enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}


来自Apple Docs(Swift 3.0.1)

You can simply use a static type property, which is guaranteed to be
lazily initialized only once, even when accessed across multiple
threads simultaneously:

1
2
3
class Singleton {
    static let sharedInstance = Singleton()
}

If you need to perform additional setup beyond initialization, you can
assign the result of the invocation of a closure to the global
constant:

1
2
3
4
5
6
7
class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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实用程序文件,我一直在使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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()
    }
}

唯一正确的方法是

1
2
3
4
5
6
7
8
9
final class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code if anything
        return instance
    }()

    private init() {}
}

访问

1
let signleton = Singleton.sharedInstance

原因:

  • 静态类型属性保证只被惰性地初始化一次,即使在多个线程同时访问时也是如此,因此不需要使用dispatch_一次
  • 私有化init方法,以便其他类无法创建实例。
  • 最后一个类,因为您不希望其他类继承单例类


我更喜欢这种实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
class APIClient {

}

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

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

在看到了david的实现之后,似乎不需要使用单实例类函数instancemethod,因为let与sharedInstance类方法做了几乎相同的事情。你需要做的就是把它声明为一个全局常量,这就是它。

1
2
3
4
5
let gScopeManagerSharedInstance = ScopeManager()

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


我的执行方式很快…

配置管理器.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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。

阅读:

1
 println(ConfigurationManager.sharedInstance.globalDic)

写:

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

1
2
3
4
5
6
7
8
9
10
11
12
   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!
}


在过去迅速实现单件化,只不过是三种方式:全局变量、内部变量和一次性调度。

这里有两个很好的单件(注意:无论什么类型的写入都必须注意私有化的init()方法。因为在swift中,所有对象的构造函数默认值都是public,需要重写init,init可以变成private,通过默认的初始化方法来防止此类"()"的其他对象创建该对象。)

方法1:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
class AppManager {
    static let sharedInstance = AppManager()

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

// How to use?
AppManager.sharedInstance


这是具有线程安全功能的最简单的一个。没有其他线程可以访问同一个singleton对象,即使它们需要。斯威夫特3/4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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!
        }
    }
}


我刚刚遇到了这个问题,但是我要求我的单例继承允许继承,而这些解决方案都不允许继承。

所以我想到了这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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,并使用相同的实例。

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

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


使用静态变量和私有初始值设定项创建singleton类。

1
2
3
4
5
6
class MySingletonClass {

    static let sharedSingleton = MySingletonClass()

    private init() {}
}


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

1
2
3
4
5
6
7
8
let TEST = Test()

class Test {

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,并提供了几个优点:

  • 提醒用户不要子类实现
  • 阻止创建其他实例
  • 确保懒惰的创建和独特的实例化
  • 通过允许以Singleton.instance的形式访问实例,缩短了语法(避免())

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    }}