Використання одиночної моделі dispatch_once в Swift


575

Я намагаюся розробити відповідну однотонну модель для використання в 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_onceSwift? Спочатку я вважав, що проблема пов'язана з блоком через ()повідомлення про помилку, але чим більше я на це дивлюся, тим більше я думаю, що це може бути питання dispatch_once_tправильного визначення.


3
Я видалив би весь цей статичний код і використав би властивість readonly з ініціалізатором @lazy.
Султан

1
Це я мав на увазі. На жаль, у нас все ще недостатньо інформації про внутрішні організації. Однак IMHO будь-яка реалізація @lazyповинна бути безпечною для потоків.
Султан

1
І цей спосіб також має перевагу в тому, щоб не піддавати реалізацію хижам абонентів.
Девід Беррі

1
Також не здається, що у вас можуть бути змінні класу @lazy.
Девід Беррі

Будь обережний! Дві речі слід зазначити при такому підході. По-перше, будь-які класи, які успадковують це, повинні мати перевагу над властивістю sharedInstance. Static.instance = TPScopeManager()примушує тип екземпляра. Якщо ви використовуєте щось на зразок Static.instance = self()необхідного ініціалізатора, буде створено відповідний клас типу. Незважаючи на це, і це важливо зазначити, лише один раз для всіх випадків в ієрархії! Перший тип для ініціалізації - це набір типів для всіх екземплярів. Я не думаю, що цілі-c поводилися так само.
sean woodward

Відповіді:


713

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 для одиничних тестів.


13
"нитка безпечна через хай" - це було заявлено десь? Я не можу знайти згадку про це в документації.
jtbandes

4
@jtbandes Константи є безпечними для потоків у всіх мовах, які я знаю.
hpique

2
@DaveWood Я припускаю, що ви говорите про останній підхід. Я цитую себе: "Я б сказав, що більше не потрібно використовувати цей підхід, але я все-таки розміщую його тут, оскільки я вважаю відмінності в синтаксисі цікавими".
hpique

5
Чи initслід також задекларувати privateгарантію, що один і лише один екземпляр об'єкта буде існувати протягом усього життя програми?
Андрій

5
У підході "Константа класу" я пропоную (а) оголосити клас finalтаким, щоб ви не підкласифікували його; та (b) оголосивши initметод privateтаким, що не можна випадково десь інстанціювати інший екземпляр.
Роб

175

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

від сюди


1
Для підтвердження: глобальні змінні мають ледачу, безпечну для потоків ініціалізацію, але змінні класу - ні. Правильно?
Білл

14
Я додам, що вдалою практикою було б оголосити ініціалізатор приватним: private init() {}- надалі застосувати той факт, що цей клас не призначений для зовнішньої інстанції.
Паскаль Бурк

1
так що ініціалізація статичної струк-структури є ледачою та безпечною для потоків, що, якщо цей статичний струк-струк є словником для мультитонів, то нам доведеться вручну синхронізувати / виклику черги до нього для кожного доступу, правда?

Якщо я правильно розумію ваше запитання, доступ до словника та масиву не є по суті безпечним для потоків, тому вам потрібно буде використовувати певну форму синхронізації потоків.
Девід Беррі

@DavidBerry Як слід викликати функцію всередині цього класу одиночки? Мені потрібна функція, яку потрібно викликати під час першого виклику myClass.sharedInstance.
Ameet Dhas

163

Для Swift 1.2 і вище:

class Singleton  {
   static let sharedInstance = Singleton()
}

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

Оновлення : тепер це офіційний спосіб визначення одиночних клавіш, як описано в офіційних документах !

Що стосується побоювань щодо використання staticпроти class. staticповинен використовуватись, навіть коли classзмінні стають доступними. Singleton не призначені для підкласів, оскільки це призведе до декількох примірників базового сингтона. Використанняstatic примушує це робити красивим, швидким способом.

Для Swift 1.0 та 1.1:

З останніми змінами в Swift, здебільшого новими методами контролю доступу, я зараз схиляюся до більш чистого способу використання глобальної змінної для одиночних клавіш.

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

Як згадується у статті про блог Свіфта тут :

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

Цей спосіб створення синглтона є безпечним, швидким, ледачим, а також з'єднується з ObjC безкоштовно.


2
Хто читає лише цю відповідь: Не забудьте зробити маркер статичним, інакше поведінка не визначена. Дивіться відредаговане питання Девіда для повного коду.
nschum

@nschum в іншому випадку поведінка не визначена, вона просто порушена чітко визначеним чином: блок завжди буде виконуватися.
Майкл

@Michael: У документації зазначено, що це не визначено. Тому поточна поведінка є випадковою.
nschum

1
Це дивно сказати. Якщо документація називає це "невизначеним", це означає, що той, хто написав код, не обіцяє того, що робить. Це не має нічого спільного з кодом, знаючи, чи є змінна статичною. Це просто означає, що на поточну (або явну) поведінку не можна покладатися.
nschum

6
Ви можете додати private init() {}як ініціалізатор SingletonClass. щоб запобігти інстанції ззовні.
rintaro

46

Swift 1.2 або новіша версія тепер підтримує статичні змінні / константи в класах. Тож ви можете просто використовувати статичну константу:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

35

Є кращий спосіб це зробити. Ви можете оголосити глобальну змінну у своєму класі над декларацією класу так:

var tpScopeManagerSharedInstance = TPScopeManager()

Це просто викликає ваш init за замовчуванням або залежно від того, що init, а глобальні змінні dispatch_onceза замовчуванням є у Swift. Тоді в якому б класі ви хочете отримати довідку, ви просто зробите це:

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

Таким чином, ви можете позбутися всього блоку коду спільного екземпляра.


3
Чому "вар" і багато "нехай"?
Стефан

1
можливо, це може бути дозволити, я тестував це лише з вар.
Кріс Гелчі

Мені подобається ця відповідь, проте мені потрібно отримати доступ до цього (Singleton) від Interface Builder. Будь-яка ідея про те, як я можу отримати доступ до цього tpScopeManagerSharedInstance зсередини ІБ ?. Спасибі.-
Луїс Паласіос

Це мій кращий спосіб мати одиночку. Він має всі звичні функції (безпека потоків і лінивий екземпляр), і він підтримує дуже легкий синтаксис: не потрібно писати TPScopeManager.sharedInstance.doIt()весь час, просто називайте свій клас TPScopeManagerClass, майте цю декларацію поруч із класом public let TPScopeManager = TPScopeManagerClass(), а при використанні просто пишіть TPScopeManager.doIt(). Дуже чисто!
Алекс

Тут нічого не заважає створювати додаткові екземпляри TPScopeManager, і тому це не є однотонним за визначенням.
Калеб

28

Swift одинаки піддаються в рамках какао в якості класу функцій, наприклад NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(). Тому має більше сенсу як функція класу відображати цю поведінку, а не змінну класу, як деякі інші рішення. наприклад:

class MyClass {

    private static let _sharedInstance = MyClass()

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

Отримайте сингл через MyClass.sharedInstance().


1
схвалено за коментар LearnCocos2D :), також за стиль.
x4h1d

2
глобальну змінну слід змінити на змінну класу через статичну внутрішню частину класу.
маль

2
@malhal, коли змінна позначена як приватна, але поза класом, вона не є глобальною - але охоплюється лише до файлу, в якому вона перебуває. Статичний всередині класу буде працювати майже так само, але я оновив відповідь, щоб використовувати статичну як ви запропонували, оскільки це краще групує змінну до класу, якщо вам трапляється використовувати кілька класів у файлі.
Райан

1
"Швидкі синглтони піддаються впливу какао-каркасів як функції класу" ... Не в Swift 3. Вони зараз зазвичай staticвластивості.
Роб

17

Згідно з документацією Apple , багато разів було повторено, що найпростіший спосіб зробити це у Swift - це властивість статичного типу:

class Singleton {
    static let sharedInstance = Singleton()
}

Однак якщо ви шукаєте спосіб здійснити додаткові налаштування поза простим викликом конструктора, секрет полягає у використанні негайно викликаного закриття:

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

Це гарантовано безпечно для потоків і ліниво ініціюється лише один раз.


як ви можете встановити статичний екземпляр нехай назад до нуля?
gpichler

1
@ user1463853 - Не можна, і взагалі не слід.
Роб

16

Швидкий 4+

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

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

2
для цього потрібен остаточний клас, ви можете пояснити більше різницю, тому що у мене є питання з іншим рішенням сингла з структурою
Raheel Sadiq

якщо це буде приватне переосмислення init () {}
NSRover

8

Дивлячись на зразок коду 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
}

Я майже впевнений, що просто використання цього статичного синтаксису за замовчуванням виконає всі дратівливі завдання.
Еоніл

на жаль, статика працює лише всередині конструкцій, тому саме ця закономірність.
user2485100

Мій намір полягав у тому, що нам не доведеться користуватися dispatch_onceпродуктами. Я роблю ставку на ваш стиль. :)
Еоніл

Чи не classв межах декларації класу еквівалент staticструктури структури?
Рассел Борогов

@Sam Так. Дивіться запис у блозі Apple у розділі Файли та ініціалізація де чітко видно, що і глобальні, і статичні члени структур та переліків користуються цією dispatch_onceможливістю.
Роб

5

Коротко,

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

Ви можете прочитати файли та ініціалізацію

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


4

Якщо ви плануєте використовувати ваш клас Singleton Swift в Objective-C, у цьому налаштуванні компілятор буде генерувати відповідні заголовки, подібні Objective-C:

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

Тоді в класі Objective-C ви можете назвати сингтона так, як ви це робили в передшвидкі дні:

[ImageStore sharedStore];

Це просто моя проста реалізація.


Це насправді більш стисло та правильніше, ніж інший приклад, оскільки він реалізований так само, як і інші одиночні кнопки Swift. тобто , як функції класу подобаються NSFileManager.defaultManager(), але до сих пір використовує ледачі механізми статичного члена поточно-Свіфт.
Леслі Годвін

Какао, як правило, реалізує їх як статичні властивості, а не як функції класу.
Роб

Мені це відомо, мені коментар старший 2 років. Дякуємо за згадування.
Майкл

4

Перше рішення

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

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

Тоді називайте це;

let shared = MySingleton.shared

Молодці для не тільки маркування initяк private, але і для створення sharedMyModelяк final! Заради майбутніх читачів, у Swift 3 ми можемо схильні перейменовуватись sharedMyModelпросто shared.
Роб

Це єдина правильна відповідь, за винятком того, що переосмислення та виклик на super.init є помилковими і навіть не компілюються.
Майкл Морріс

4

Використання:

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

Це точно так само, як одна з відповідей, яку я пройшов на шляху до поточної відповіді. Оскільки глобальні змінні ініціалізуються як ледачі, так і безпечні для потоків, немає ніякої причини для додаткової складності.
Девід Беррі

@David Крім того, що не має глобальної змінної. :)
hpique

@hpique ні, точно як одна з моїх попередніх спроб. Подивіться історію редагування.
Девід Беррі

4

Найкращий підхід у Swift вище 1,2 - однолінійний однотонний, як -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

Щоб дізнатися більше про цей підхід, ви можете відвідати це посилання .


Чому NSObjectпідклас ?. Крім цього, це, здається, по суті те саме, що і stackoverflow.com/a/28436202/1187415 .
Мартін Р

3

Від 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
    }()
}

3

Я б запропонував enum, як ви використовуєте на Java, наприклад

enum SharedTPScopeManager: TPScopeManager {
    case Singleton
}

IMO, це єдиний правильний спосіб швидкого впровадження Singleton. інші відповіді - ObjC / C / C ++ way
Bryan Chen

Не могли б ви детальніше розглянути цю відповідь? Мені незрозуміло, де Сінглтон примірник цього фрагмента
Кенні Вінкер

@KennyWinker У мене немає входу для розробників Apple, тому немає швидкого і тому я не можу відповісти, коли відбувається ініціалізація. На Java це на першому використанні. Можливо, ви можете спробувати його з друком при ініціалізації та побачити, чи відбувається друк при запуску чи після доступу. Це залежатиме від того, як enum реалізується компілятором.
Говард Ловатт

@KennyWinkler: Apple щойно роз’яснила, як це працює, дивіться сторінку developer.apple.com/swift/blog/?id=7 . У ньому вони кажуть "запустити ініціалізатор для глобального вперше на нього посилання, подібно до Java", і зокрема. Вони також кажуть, що під обкладинками вони використовують "dispatch_once, щоб переконатися, що ініціалізація є атомною". Тому переконання майже напевно - шлях, якщо у вас є якийсь фантазійний ініт зробити, тоді приватне статичне дозвілля - це рішення.
Говард Ловатт

2

Для ознайомлення, ось приклад одиночної реалізації реалізації вкладеної структури 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()
    }
}

2

Швидко ви можете створити однотонний клас наступним чином:

class AppSingleton: NSObject {

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

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

1

Я віддаю перевагу цій реалізації:

class APIClient {

}

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

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

1

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

1

Єдиний правильний підхід - нижче.

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

    private init() {}
}

Доступу

let signleton = Singleton.sharedInstance

Причини:

  • static властивість типу гарантується, що ліниво ініціалізується лише один раз, навіть при доступі до декількох потоків одночасно, тому не потрібно використовувати dispatch_once
  • Приватизація init методу, тому екземпляр не може бути створений іншими класами.
  • final клас, оскільки ви не хочете, щоб інші класи успадкували клас Singleton.

Чому ви використовували ініціалізацію закриття, тоді як можете безпосередньо використовуватиstatic let sharedInstance = Singleton()
abhimuralidharan

1
якщо ви не хочете робити додаткових налаштувань, то те, що ви говорите, правильно.
applefreak

1

Побачивши реалізацію Девіда, здається, що немає необхідності мати функцію класу однотонних, instanceMethodоскільки letце робить майже те саме, що і sharedInstanceметод класу. Все, що вам потрібно зробити - це оголосити це глобальною константою, і це було б все.

let gScopeManagerSharedInstance = ScopeManager()

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

2
Як я кажу у своїх коментарях, єдиною причиною цього є те, що в певний момент в майбутньому ви можете перемістити / приховати глобальну змінну і отримати більш подібну поведінку. У той момент, якщо все використовує послідовний зразок, ви можете просто змінити самі класи синглів, не змінюючи використання.
Девід Беррі

0
   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 з цієї причини.
Девід Беррі

0

Швидко усвідомити синглтон в минулому - це не що інше, як три способи: глобальні змінні, внутрішні змінні та способи 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

-1

Це найпростіший варіант з безпекою потокових можливостей. Жоден інший потік не може отримати доступ до того самого однотонного об'єкта, навіть якщо вони хочуть. Швидкий 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!
        }
    }
}

2
Яка перевага перед властивістю статичного типу (яке гарантовано ліниво ініціалізується лише один раз, навіть при доступі до декількох потоків одночасно)?
Мартін Р

-1

Я вимагав від свого одиночного дозволу на спадщину, і жодне з цих рішень насправді цього не дозволяло. Тому я придумав таке:

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вірно і використовується той же екземпляр.

Проблема з цим першим брудним підходом полягає в тому, що я не можу гарантувати, що підкласи будуть реалізовувати dispatch_once_tта переконуватись, що sharedInstanceVarце буде змінено лише один раз у класі.

Я спробую це ще більше уточнити, але було б цікаво побачити, чи є у когось сильні почуття проти цього (окрім того, що він є багатослівним і вимагає його вручну оновити).



-2

Я використовую такий синтаксис:

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. Скорочує синтаксис (уникає ()), дозволяючи отримати доступ до екземпляра як Singleton.instance
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.