Использование однократной модели dispatch в Swift
Я пытаюсь разработать соответствующую одноэлементную модель для использования в Swift. До сих пор я смог получить не-потокобезопасную модель, работающую как:
class var sharedInstance:TPScopeManager {
get {
struct Static {
static var instance : TPScopeManager? = nil
}
if !Static.instance {
Static.instance = TPScopeManager()
}
return Static.instance!
}
}
обертывание одноэлементного экземпляра в статической структуре должно позволить одному экземпляру, который не сталкивается с одноэлементными экземплярами без сложных схем именования, и это должно сделать вещи довольно частными. Очевидно, что эта модель не является потокобезопасной, поэтому я попытался добавить 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
правильно определен.
29 ответов
tl; dr: используйте постоянный класс подход, если вы используете Swift 1.2 или выше и вложенные структуры подход, если вам нужно поддерживать более ранние версии.
из моего опыта работы с Swift существует три подхода к реализации Одноэлементного шаблона, которые поддерживают ленивую инициализацию и безопасность потоков.
постоянный класс
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
vs 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() {
// ...
}
}
есть лучший способ сделать это. Вы можете объявить глобальную переменную в своем классе над классом decleration как so
var tpScopeManagerSharedInstance = TPScopeManager()
Это просто вызывает ваш init по умолчанию или какие-либо init и глобальные переменные dispatch_once по умолчанию в Swift. Затем в каком бы классе вы не хотели получить ссылку, вы просто делаете это:
var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()
таким образом, в основном вы можете избавиться от всего блока общего кода экземпляра.
Swift синглеты подвергаются в рамках Cocoa как функции класса, например 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
}()
}
это гарантированно будет потокобезопасным и лениво инициализирован только один раз.
Swift 4+
protocol Singleton: class {
static var sharedInstance: Self { get }
}
final class Kraken: Singleton {
static let sharedInstance = Kraken()
private init() {}
}
глядя на пример кода Apple, я наткнулся на этот шаблон. Я не уверен, как быстро работает со статикой,но это было бы безопасно в C#. Я включаю как свойство, так и метод для взаимодействия Objective-C.
struct StaticRank {
static let shared = RankMapping()
}
class func sharedInstance() -> RankMapping {
return StaticRank.shared
}
class var shared:RankMapping {
return StaticRank.shared
}
Если вы планируете использовать свой класс Swift singleton в Objective-C, Эта настройка будет иметь компилятор, генерирующий соответствующий заголовок(ы) Objective-C:
class func sharedStore() -> ImageStore {
struct Static {
static let instance : ImageStore = ImageStore()
}
return Static.instance
}
тогда в классе Objective-C вы можете назвать свой синглтон так, как вы это делали в дни до Swift:
[ImageStore sharedStore];
Это просто моя простая реализация.
первый вариант
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()
}
использование:
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)")
короче,
class Manager {
static let sharedInstance = Manager()
private init() {}
}
Вы можете ознакомиться файлы и инициализации
ленивая инициализация глобальной переменной (также для статических членов структуры и перечисления) запускается при первом доступе к глобальному и запускается как
dispatch_once
чтобы убедиться, что инициализация атомный.
final class MySingleton {
private init() {}
static let shared = MySingleton()
}
затем назвать его;
let shared = MySingleton.shared
Я бы предложил перечисление, как вы бы использовали в Java, например:
enum SharedTPScopeManager: TPScopeManager {
case Singleton
}
просто для справки, вот пример одноэлементной реализации вложенной реализации структуры 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()
}
}
лучший подход в Swift выше 1.2-однострочный синглтон, как -
class Shared: NSObject {
static let sharedInstance = Shared()
private override init() { }
}
чтобы узнать больше об этом подходе, вы можете посетить этот ссылке.
Я предпочитаю эту реализацию:
class APIClient {
}
var sharedAPIClient: APIClient = {
return APIClient()
}()
extension APIClient {
class func sharedClient() -> APIClient {
return sharedAPIClient
}
}
мой способ реализации в Swift...
ConfigurationManager.Свифт!--7-->
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 Docs (Swift 3.0.1),
вы можете просто использовать свойство статического типа, которое гарантированно лениво инициализируется только один раз, даже при доступе через несколько темы одновременно:
class Singleton {
static let sharedInstance = Singleton()
}
Если вам нужно выполнить дополнительную установку за инициализацией, то вы можете присвоить результат вызова закрытия глобальному константа:
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}
после просмотра реализации Дэвида кажется, что нет необходимости иметь одноэлементную функцию класса 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!
}
Я только что столкнулся с этим, но я требовал, чтобы мой синглтон разрешал наследование, и ни одно из этих решений на самом деле не разрешало его.
Итак, я придумал это:
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.
- если выше сделать, то Субсинглтон.sharedInstance () является синглтоном true и используется тот же экземпляр.
проблема с этим первым грязным подходом заключается в том, что я не могу гарантировать, что подклассы будут реализовывать dispatch_once_t и убедитесь, что sharedInstanceVar изменяется только один раз для класса...
Я попытаюсь уточнить это дальше, но было бы интересно посмотреть, есть ли у кого-то сильные чувства против этого (кроме того, что это многословно и требует вручную обновлять его.)
Это самый простой с потокобезопасными возможностями. Никакой другой поток не может получить доступ к тому же одноэлементному объекту, даже если они хотят. 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!
}
}
}
Swift для реализации singleton в прошлом-это не что иное, как три способа: глобальные переменные, внутренние переменные и способы dispatch_once.
вот два хороших синглтона.(Примечание: независимо от того, какое письмо будет обращать внимание на метод приватизации init ().Поскольку в Swift все конструкторы объекта по умолчанию являются общедоступными, необходимо переписать 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
Это моя реализация. Это также мешает программисту создавать новый экземпляр:
let TEST = Test()
class Test {
private init() {
// This is a private (!) constructor
}
}
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, и предлагает несколько добродетелей:
- напоминает пользователю не к реализации подкласса
- предотвращает создание дополнительных экземпляров
- обеспечивает ленивое создание и уникальный экземпляр
- сокращает синтаксис (избегает ()), позволяя получить доступ к примеру, как
Singleton.instance
используйте статическую переменную и частный инициализатор для создания одноэлементного класса.
class MySingletonClass {
static let sharedSingleton = MySingletonClass()
private init() {}
}