Использование одноэлементной модели dispatch_once в Swift

Я пытаюсь разработать подходящую модель синглтона для использования в 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() })

Как правильно использовать dispatch_once используя Swift? Я изначально думал, что проблема была с блоком из-за () в сообщении об ошибке, но чем больше я смотрю на него, тем больше я думаю, что это может быть вопросом получения dispatch_once_t правильно определено.

30 ответов

Решение

tl;dr: Используйте подход с константой класса, если вы используете Swift 1.2 или выше, и подход с вложенной структурой, если вам нужно поддерживать более ранние версии.

Исходя из моего опыта работы со Swift, существует три подхода к реализации шаблона Singleton, которые поддерживают отложенную инициализацию и безопасность потоков.

Константа класса

class Singleton  {
   static let sharedInstance = Singleton()
}

Этот подход поддерживает ленивую инициализацию, потому что Swift лениво инициализирует константы (и переменные) класса и является потокобезопасным по определению let, Это официально рекомендованный способ создания экземпляра синглтона.

Классовые константы были введены в 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 для модульных тестов.

Поскольку 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 в вашем коде: просто объявите глобальную переменную с инициализатором и отметьте ее как приватную.

Отсюда

Для Swift 1.2 и выше:

class Singleton  {
   static let sharedInstance = Singleton()
}

С доказательством правильности (здесь все заслуги), практически нет причин использовать какой-либо из предыдущих методов для синглетонов.

Обновление: теперь это официальный способ определения синглетонов, как описано в официальных документах!

Что касается опасений по поводу использования static против class, static должен использоваться один раз, когда class переменные становятся доступными. Синглтоны не должны быть разделены на подклассы, так как это приведет к нескольким экземплярам базового синглтона. С помощью static обеспечивает это красивым, быстрым способом.

Для Swift 1.0 и 1.1:

С недавними изменениями в Swift, в основном новыми методами контроля доступа, я теперь склоняюсь к более чистому способу использования глобальной переменной для синглетонов.

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

Как упомянуто в статье блога Swift здесь:

Ленивый инициализатор для глобальной переменной (также для статических членов структур и перечислений) запускается при первом обращении к глобальному и запускается как dispatch_once, чтобы убедиться, что инициализация является атомарной. Это дает отличный способ использовать dispatch_once в вашем коде: просто объявите глобальную переменную с инициализатором и отметьте ее как приватную.

Этот способ создания синглтона является потокобезопасным, быстрым, ленивым, а также подключен к ObjC бесплатно.

Swift 1.2 или новее теперь поддерживает статические переменные / константы в классах. Так что вы можете просто использовать статическую константу:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

Есть лучший способ сделать это. Вы можете объявить глобальную переменную в вашем классе над объявлением класса следующим образом

var tpScopeManagerSharedInstance = TPScopeManager()

Это просто вызывает вашу инициализацию по умолчанию или любую другую инициализацию, а глобальные переменные по умолчанию в Swift - dispatch_once. Затем в любом классе вы хотите получить ссылку, вы просто делаете это:

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

Таким образом, вы можете избавиться от всего блока совместно используемого кода экземпляра.

Быстрые синглтоны представляются в рамках Какао как функции класса, например NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter()Я чувствую, что в качестве функции класса более разумно отражать это поведение, а не переменную класса, которую используют некоторые другие решения, например

class MyClass {

    private static let _sharedInstance = MyClass()

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

Получить синглтон через MyClass.sharedInstance(),

Согласно документации Apple, много раз повторялось, что самый простой способ сделать это в Swift - использовать свойство статического типа:

class Singleton {
    static let sharedInstance = Singleton()
}

Однако, если вы ищете способ выполнить дополнительную настройку помимо простого вызова конструктора, секрет заключается в том, чтобы использовать немедленно вызванное замыкание:

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

Это гарантированно поточно-ориентировано и лениво инициализируется только один раз.

Свифт 4+

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

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

Глядя на пример кода 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
}

Если вы планируете использовать свой одноэлементный класс Swift в Objective-C, эта установка заставит компилятор генерировать соответствующие заголовки (-и), подобные Objective-C:

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

Затем в классе Objective-C вы можете назвать свой синглтон так, как вы это делали в пред-Свифтские дни:

[ImageStore sharedStore];

Это просто моя простая реализация.

Вкратце,

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

Вы можете прочитать файлы и инициализация

Ленивый инициализатор для глобальной переменной (также для статических членов структур и перечислений) запускается при первом обращении к глобальному и запускается как dispatch_once чтобы убедиться, что инициализация является атомарной.

Использование:

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

Первое решение

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

Лучший подход в Swift выше 1.2 - это однострочный синглтон, так как -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

Чтобы узнать больше об этом подходе вы можете посетить эту ссылку.

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

Тогда назови это;

let shared = MySingleton.shared

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

Просто для справки, вот пример реализации Singleton реализации Nested Struct Джека Ву / 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()
    }
}

Я бы предложил Enum, как вы бы использовали в Java, например:

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

Я предпочитаю эту реализацию:

class APIClient {

}

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

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

Мой способ реализации в 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

В swift вы можете создать одноэлементный класс следующим образом:

class AppSingleton: NSObject {

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

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

После просмотра реализации Дэвида кажется, что нет необходимости иметь функцию класса singleton instanceMethod, так как let выполняет почти то же самое, что и метод класса sharedInstance. Все, что вам нужно сделать, это объявить его как глобальную константу, и это будет так.

let gScopeManagerSharedInstance = ScopeManager()

class ScopeManager {
 // No need for a class method to return the shared instance. Use the gScopeManagerSharedInstance directly. 
}
   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!
}

Единственный правильный подход ниже

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

Swift 5.2

Вы можете указать на тип с помощью Self. Так:

static let shared = Self()

И должен быть внутри типа, например:

class SomeTypeWithASingletonInstance {
   static let shared = Self()
}

Быстрая реализация синглтона в прошлом - это не что иное, как три способа: глобальные переменные, внутренние переменные и способы dispatch_once.

Вот два хороших синглтона.(Примечание: независимо от того, какой вид написания должен обращать внимание на метод приватизации init (). Так как в Swift все конструкторы объекта по умолчанию являются публичными, их нужно переписать, init можно превратить в приватный), запретить другим объектам этого класса '()' по умолчанию метод инициализации для создания объекта.)

Способ 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
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
}}

Я склонен использовать следующий синтаксис как наиболее полный:

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. Сокращает синтаксис (avoids ()), позволяя получить доступ к экземпляру как Singleton.instance

Используйте статическую переменную и частный инициализатор для создания одноэлементного класса.

class MySingletonClass {

    static let sharedSingleton = MySingletonClass()

    private init() {}
}

Это самый простой с поточно-ориентированными возможностями. Никакой другой поток не может получить доступ к тому же объекту-одиночке, даже если он хочет Свифт 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!
        }
    }
}
Другие вопросы по тегам