我正在努力制定一个合适的单例模型用于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的问题。


当前回答

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

其他回答

这里有一个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()
    }
}

这是具有线程安全功能的最简单的一个。即使其他线程想要访问同一个单例对象,它们也不能访问。斯威夫特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!
        }
    }
}

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

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

let TEST = Test()

class Test {

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

唯一正确的方法如下。

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类。