Использование одноэлементной модели 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 и предлагает несколько достоинств:
- Напоминает пользователю не использовать реализацию подкласса
- Предотвращает создание дополнительных экземпляров
- Гарантирует ленивое создание и уникальную реализацию
- Сокращает синтаксис (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!
}
}
}