with - потокобезопасный синглтон swift




Использование модели singleton dispatch_once в Swift (20)

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


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

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

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

class MyClass {

    private static let _sharedInstance = MyClass()

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

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


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

class Singleton {
    static let sharedInstance = Singleton()
}

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

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

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


Вкратце,

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

Возможно, вы захотите ознакомиться с here

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


Для справки, здесь приведен пример реализации Singleton реализации вложенного Struct 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()
    }
}

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

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

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

var tpScopeManagerSharedInstance = TPScopeManager()

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

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

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


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

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

Мой способ внедрения в 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

Поскольку 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


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

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

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

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

Поэтому я придумал это:

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

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


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


Для Swift 1.2 и выше:

class Singleton  {
   static let sharedInstance = Singleton()
}

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

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

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

Для Swift 1.0 и 1.1:

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

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

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

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

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


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

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

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

   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 MySingleton {
     private init() {}
     static let shared = MySingleton()
}

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

let shared = MySingleton.shared




dispatch