Использование модели singleton 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!
    }
}

Объединение экземпляра singleton в Static struct должно позволить одному экземпляру, который не сталкивается с экземплярами singleton без сложных схем именования, и должен сделать вещи довольно конфиденциальными. Очевидно, что эта модель не является потокобезопасной, поэтому я попытался добавить 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.

Ответ 1

tl; dr: используйте подход class constant, если вы используете 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 проект для модульных тестов.

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

От здесь

Ответ 3

Для Swift 1.2 и выше:

class Singleton  {
   static let sharedInstance = Singleton()
}

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

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

Что касается проблем использования 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 здесь:

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

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

Ответ 4

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

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

Ответ 5

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

var tpScopeManagerSharedInstance = TPScopeManager()

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

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

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

Ответ 6

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

class MyClass {

    private static let _sharedInstance = MyClass()

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

Извлеките одноэлемент через MyClass.sharedInstance().

Ответ 7

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

class Singleton {
    static let sharedInstance = Singleton()
}

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

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

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

Ответ 8

Swift 4 +

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

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

Ответ 9

Глядя на образец кода Apple, я наткнулся на этот шаблон. Я не уверен, как Swift имеет дело со статикой, но в С# это будет потокобезопасным. Я включаю как свойство, так и метод для Objective-C interop.

struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}

Ответ 10

Вкратце,

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

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

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

Ответ 11

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

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

Ответ 12

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

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

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

[ImageStore sharedStore];

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

Ответ 13

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

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

Ответ 14

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

Затем назовите его

let shared = MySingleton.shared

Ответ 15

Лучший подход в Swift выше 1.2 - однострочный однострочный, as -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

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

Ответ 16

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

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

Ответ 17

От Apple Документы (Swift 3.0.1),

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

class Singleton {
    static let sharedInstance = Singleton()
}

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

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

Ответ 18

Просто для справки, вот пример реализации Singleton реализации Jack Wu/hpique Nested Struct. Реализация также показывает, как может работать архивирование, а также некоторые сопутствующие функции. Я не мог найти этот полный пример, так что, надеюсь, это поможет кому-то!

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

Ответ 19

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

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

Ответ 20

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

let gScopeManagerSharedInstance = ScopeManager()

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

Ответ 21

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

class APIClient {

}

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

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

Ответ 22

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

Ответ 23

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

Ответ 24

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

Вот два хороших синглтона (обратите внимание: независимо от того, какой тип письма должен обратить внимание на метод privatisation 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

Ответ 25

Вышеупомянутое решение работает, но ни одно из этих решений не учитывает параметризованные случаи конструктора. Также эти синглы будут инициализированы в начале, но в некоторых случаях требуется отложенная инициализация, поэтому подход должен отличаться в этом случае. Статическая переменная sharedInstance не должна предоставляться напрямую из класса, поскольку ее значение будет использоваться с использованием метода getInstance, поскольку она может содержать код инициализации объекта в первый раз и может использоваться для отложенной инициализации.

Ответ 26

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

class AppSingleton: NSObject {

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

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

Ответ 27

Я только что наткнулся на это, но я потребовал, чтобы мой синглтон разрешал наследование, и ни одно из этих решений на самом деле не разрешало его.

Итак, я придумал это:

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 изменяется только один раз за класс...

Я попытаюсь уточнить это дальше, но было бы интересно узнать, есть ли у кого-то сильные чувства против этого (помимо того факта, что он подробный и требует его вручную обновить).

Ответ 28

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

Ответ 29

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

class MySingletonClass {

    static let sharedSingleton = MySingletonClass()

    private init() {}
}

Ответ 30

Это моя реализация. Это также мешает программисту создать новый экземпляр:

let TEST = Test()

class Test {

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