Я пытаюсь разработать подходящую модель синглтон для использования в 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
.
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 и более ранних версиях и по-прежнему работает в качестве обходного пути для отсутствия статических констант и переменных в функциях.
Традиционный подход 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 в своем коде: просто объявите глобальную переменную с инициализатором и пометьте ее конфиденциальной.
От здесь
private init() {}
, чтобы еще больше усилить тот факт, что этот класс не предназначен для внешнего применения.
Для 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 бесплатно.
Swift 1.2 или более поздняя версия теперь поддерживает статические переменные/константы в классах. Таким образом, вы можете просто использовать статическую константу:
class MySingleton {
static let sharedMySingleton = MySingleton()
private init() {
// ...
}
}
Есть лучший способ сделать это. Вы можете объявить глобальную переменную в своем классе над объявлением класса следующим образом
var tpScopeManagerSharedInstance = TPScopeManager()
Это просто вызывает инициализацию по умолчанию или любой из параметров init и глобальных переменных dispatch_once по умолчанию в Swift. Затем в любом классе вы хотите получить ссылку, вы просто выполните следующее:
var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()
Таким образом, вы можете избавиться от всего блока кода общего экземпляра.
Свифт-синглтоны раскрываются в рамках Cocoa как функции класса, например. NSFileManager.defaultManager()
, NSNotificationCenter.defaultCenter()
, поэтому я чувствую, что более разумно рассматривать функцию класса, чтобы отражать это поведение, а не переменную класса, как используют некоторые другие решения, например
class MyClass {
private static let _sharedInstance = MyClass()
class func sharedInstance() -> MyClass {
return _sharedInstance
}
}
Извлеките одноэлемент через MyClass.sharedInstance()
.
Swift 4 +
protocol Singleton: class {
static var sharedInstance: Self { get }
}
final class Kraken: Singleton {
static let sharedInstance = Kraken()
private init() {}
}
В документации Apple было повторено много раз, что самый простой способ сделать это в Swift - это свойство статического типа:
class Singleton {
static let sharedInstance = Singleton()
}
Однако, если вы ищете способ выполнить дополнительную настройку за пределами простого вызова конструктора, секрет заключается в том, чтобы использовать сразу вызываемое закрытие:
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}
Это гарантировано для потокобезопасности и лениво инициализируется только один раз.
Глядя на образец кода 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
}
Первое решение
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 в Objective-C, эта настройка будет иметь компилятор для создания соответствующих Objective-C -подобных заголовков:
class func sharedStore() -> ImageStore {
struct Static {
static let instance : ImageStore = ImageStore()
}
return Static.instance
}
Затем в классе Objective-C вы можете назвать свой синглтон так, как вы это делали в дни до Swift:
[ImageStore sharedStore];
Это просто моя простая реализация.
NSFileManager.defaultManager()
, но все еще использует ленивые поточно- NSFileManager.defaultManager()
механизмы статического члена Swift.
Использование:
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() {}
}
Возможно, вы захотите прочитать Файлы и инициализация
ленивый инициализатор для глобальной переменной (также для статических членов structs и enums) запускается в первый раз, когда глобальный доступ доступен, и запускается как
dispatch_once
, чтобы убедиться, что инициализация атомарный.
final class MySingleton {
private init() {}
static let shared = MySingleton()
}
Затем назовите его
let shared = MySingleton.shared
init
как private
, но и для того, чтобы сделать sharedMyModel
final
! Ради будущих читателей, в Swift 3, мы могли бы быть склонны переименовать sharedMyModel
быть просто shared
.
Лучший подход в Swift выше 1.2 - однострочный однострочный, as -
class Shared: NSObject {
static let sharedInstance = Shared()
private override init() { }
}
Чтобы узнать больше об этом подходе, вы можете посетить ссылку .
NSObject
? Кроме того, это похоже на то же самое, что и stackoverflow.com/a/28436202/1187415 .
Просто для справки, вот пример реализации 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()
}
}
Я бы предложил Enum, как вы бы использовали в Java, например:
enum SharedTPScopeManager: TPScopeManager {
case Singleton
}
От Apple Документы (Swift 3.0.1),
Вы можете просто использовать свойство статического типа, которое гарантировано лениво инициализируется только один раз, даже если доступ к ним осуществляется через несколько потоки одновременно:
class Singleton {
static let sharedInstance = Singleton()
}
Если вам нужно выполнить дополнительную настройку после инициализации, вы можете назначить результат вызова закрытия глобальному константа:
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}
Мой способ реализации в 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
Я предпочитаю эту реализацию:
class APIClient {
}
var sharedAPIClient: APIClient = {
return APIClient()
}()
extension APIClient {
class func sharedClient() -> APIClient {
return sharedAPIClient
}
}
После просмотра реализации 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.
}
Единственный правильный подход ниже
final class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code if anything
return instance
}()
private init() {}
}
Доступ
let signleton = Singleton.sharedInstance
Причины:
static let sharedInstance = Singleton()
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!
}
dispatch_once
поскольку инициализация статической переменной является ленивой и автоматически защищается через dispatch_once
Apple фактически рекомендует использовать статику вместо dispatch_once по этой причине.
Вышеупомянутое решение работает, но ни одно из этих решений не учитывает параметризованные случаи конструктора. Также эти синглы будут инициализированы в начале, но в некоторых случаях требуется отложенная инициализация, поэтому подход должен отличаться в этом случае. Статическая переменная sharedInstance не должна предоставляться напрямую из класса, поскольку ее значение будет использоваться с использованием метода getInstance, поскольку она может содержать код инициализации объекта в первый раз и может использоваться для отложенной инициализации.
Быстро реализовать синглтон в прошлом - это не что иное, как три способа: глобальные переменные, внутренние переменные и пути 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
Это самый простой вариант с поддержкой потоков. Ни один другой поток не может получить доступ к одному и тому же объекту 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!
}
}
}
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 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
}
}
Проблема с этим первым грязным подходом заключается в том, что я не могу гарантировать, что подклассы будут реализовывать dispatch_once_t и убедиться, что sharedInstanceVar изменяется только один раз за класс...
Я попытаюсь уточнить это дальше, но было бы интересно узнать, есть ли у кого-то сильные чувства против этого (помимо того факта, что он подробный и требует его вручную обновить).
Это моя реализация. Это также мешает программисту создать новый экземпляр:
let TEST = Test()
class Test {
private init() {
// This is a private (!) constructor
}
}
private init
: stackoverflow.com/a/28436202/1187415 .
Я использую следующий синтаксис как наиболее полный:
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() {}
}
@lazy
должна быть поточно-@lazy
.