Korzystanie z modelu singletonowego dispatch_once w Swift


575

Próbuję wypracować odpowiedni model singletonu do użytku w Swift. Do tej pory udało mi się uzyskać bezpieczny dla wątków model działający jako:

class var sharedInstance: TPScopeManager {
    get {
        struct Static {
            static var instance: TPScopeManager? = nil
        }

        if !Static.instance {
            Static.instance = TPScopeManager()
        }

        return Static.instance!
    }
}

Zawijanie instancji singletonowej w strukturze statycznej powinno pozwolić jednej instancji, która nie koliduje z instancjami singletonowymi bez skomplikowanych schematów nazewnictwa, i powinna uczynić to dość prywatnym. Oczywiście ten model nie jest bezpieczny dla wątków. Próbowałem więc dodać dispatch_oncedo całości:

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!
    }
}

Ale pojawia się błąd kompilatora w dispatch_oncewierszu:

Nie można przekonwertować typu wyrażenia „Void” na typ „()”

Wypróbowałem kilka różnych wariantów składni, ale wszystkie wydają się mieć takie same wyniki:

dispatch_once(Static.token, { Static.instance = TPScopeManager() })

Jakie jest właściwe użycie dispatch_onceSwift? Początkowo myślałem, że problem dotyczy bloku z powodu ()komunikatu o błędzie, ale im dłużej na niego patrzę, tym bardziej myślę, że może to być kwestia dispatch_once_tprawidłowego zdefiniowania.


3
Chciałbym usunąć cały ten kod statyczny i użyć właściwości tylko do odczytu z inicjatorem @lazy.
Sulthan

1
O to mi chodziło. Niestety wciąż nie mamy wystarczających informacji na temat elementów wewnętrznych. Jednak każda implementacja IMHO @lazypowinna być bezpieczna dla wątków.
Sulthan

1
I w ten sposób ma tę zaletę, że nie naraża implementacji na drapieżniki dzwoniących.
David Berry

1
Nie wygląda też na to, że możesz mieć zmienne klasy @lazy.
David Berry

Bądź ostrożny! Przy tym podejściu należy zwrócić uwagę na dwie rzeczy. Po pierwsze, wszelkie dziedziczące po nim klasy będą musiały zastąpić właściwość sharedInstance. Static.instance = TPScopeManager()wymusza typ instancji. Jeśli użyjesz czegoś takiego Static.instance = self()z wymaganym inicjatorem, zostanie wygenerowana odpowiednia klasa typu. Mimo to jest to ważna rzecz do odnotowania, tylko raz dla wszystkich instancji w hierarchii! Pierwszym typem do zainicjowania jest zestaw typów dla wszystkich instancji. Nie sądzę, żeby cel-c zachowywał się tak samo.
Sean Woodward

Odpowiedzi:


713

tl; dr: Użyj metody stałej klasowej, jeśli używasz Swift 1.2 lub nowszej, i zagnieżdżonej struktury strukturalnej, jeśli potrzebujesz obsługi wcześniejszych wersji.

Z mojego doświadczenia ze Swiftem są trzy podejścia do implementacji wzorca Singleton, które wspierają leniwe inicjowanie i bezpieczeństwo wątków.

Stała klasy

class Singleton  {
   static let sharedInstance = Singleton()
}

To podejście obsługuje leniwe inicjowanie, ponieważ Swift leniwie inicjuje stałe klas (i zmienne) i jest wątkowo bezpieczny z definicji let. Jest to teraz oficjalnie zalecany sposób utworzenia singletonu.

Stałe klas zostały wprowadzone w Swift 1.2. Jeśli potrzebujesz obsługiwać wcześniejszą wersję Swift, użyj poniższego podejścia do struktury zagnieżdżonej lub stałej globalnej.

Struktura zagnieżdżona

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

Używamy tutaj stałej statycznej zagnieżdżonej struktury jako stałej klasy. Jest to obejście problemu braku stałych statycznych klas w Swift 1.1 i wcześniejszych wersjach, i nadal działa jako obejście braku stałych statycznych i zmiennych w funkcjach.

dispatch_once

Tradycyjne podejście do celu C przeniesiono do Swift. Jestem całkiem pewien, że nie ma przewagi nad zagnieżdżoną strukturą strukturalną, ale i tak ją tutaj umieszczam, ponieważ uważam różnice w składni za interesujące.

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!
    }
}

Zobacz ten projekt GitHub dla testów jednostkowych.


13
„wątek bezpieczny dzięki let” - czy gdziekolwiek to stwierdzono? Nie mogę znaleźć wzmianki o tym w dokumentacji.
jtbandes

4
@jtbandes Stałe są bezpieczne dla wątków we wszystkich językach, które znam.
hpique

2
@DaveWood Zakładam, że mówisz o ostatnim podejściu. Cytuję siebie: „Powiedziałbym, że nie jest już konieczne stosowanie tego podejścia, ale umieszczam je tutaj, ponieważ uważam różnice w składni za interesujące”.
hpique

5
Czy initnależy również zadeklarować, privateaby zagwarantować, że przez całe życie aplikacji będzie istniała tylko jedna instancja obiektu?
Andrew

5
W podejściu „Stała klasy” sugerowałbym (a) zadeklarowanie klasy, aby finalnie była podklasą; oraz (b) oświadczenie, że initmetoda jest privatetaka, że ​​nie można przypadkowo utworzyć gdzieś innej instancji.
Rob

175

Ponieważ Apple wyjaśniło, że statyczne zmienne struktury są inicjowane zarówno leniwie, jak i zawinięte dispatch_once(patrz uwaga na końcu postu), myślę, że moim ostatecznym rozwiązaniem będzie:

class WithSingleton {
    class var sharedInstance: WithSingleton {
        struct Singleton {
            static let instance = WithSingleton()
        }

        return Singleton.instance
    }
}

Wykorzystuje to automatyczną, leniwą, bezpieczną dla wątków inicjalizację statycznych elementów konstrukcyjnych, bezpiecznie ukrywa rzeczywistą implementację przed konsumentem, utrzymuje wszystko w kompaktowych przedziałach dla czytelności i eliminuje widoczną zmienną globalną.

Apple wyjaśniło, że leniwy inicjator jest bezpieczny dla wątków, więc nie ma potrzeby dispatch_onceani podobnych zabezpieczeń

Leniwy inicjator zmiennej globalnej (także dla statycznych elementów struktur i wyliczeń) jest uruchamiany przy pierwszym dostępie do globu i uruchamiany jako dispatch_once, aby upewnić się, że inicjalizacja jest atomowa. Umożliwia to fajny sposób użycia dispatch_once w kodzie: wystarczy zadeklarować zmienną globalną za pomocą inicjatora i oznaczyć ją jako prywatną.

od tutaj


1
Aby potwierdzić: zmienne globalne mają opóźnioną, bezpieczną dla wątków inicjalizację, ale zmienne klasowe nie. Dobrze?
Bill

14
Dodałbym, że dobrą praktyką byłoby zadeklarowanie inicjatora jako prywatnego: private init() {}w celu dalszego egzekwowania faktu, że ta klasa nie jest przeznaczona do tworzenia instancji z zewnątrz.
Pascal Bourque,

1
więc inicjalizacja statycznej struktury var jest leniwa i bezpieczna dla wątków, co jeśli jeśli ta statyczna struktura var jest słownikiem dla multitonów, to musimy ręcznie synchronizować / kolejkować do niej połączenia dla każdego dostępu, prawda?

Jeśli dobrze rozumiem twoje pytanie, dostęp do słownika i tablicy nie jest z natury bezpieczny dla wątków, więc będziesz musiał użyć jakiejś formy synchronizacji wątków.
David Berry

@DavidBerry Jak powinienem wywołać funkcję w tej klasie singletonów? Potrzebuję funkcji do wywołania przy pierwszym wywołaniu myClass.sharedInstance.
Ameet Dhas,

163

W przypadku Swift 1.2 i nowszych wersji:

class Singleton  {
   static let sharedInstance = Singleton()
}

Z dowodem poprawności (wszystkie zasługi tutaj ), nie ma teraz żadnego powodu, aby używać dowolnej z poprzednich metod singletonów.

Aktualizacja : Jest to teraz oficjalny sposób definiowania singletonów, jak opisano w oficjalnych dokumentach !

Jeśli chodzi o obawy dotyczące korzystania staticvs class. staticpowinien być tym, którego należy używać, nawet gdy classzmienne stają się dostępne. Singletonów nie należy klasyfikować do podklas, ponieważ spowodowałoby to wiele wystąpień podstawowego singletonu. Używanie staticwymusza to w piękny, szybki sposób.

W przypadku Swift 1.0 i 1.1:

Dzięki ostatnim zmianom w Swift, głównie nowym metodom kontroli dostępu, teraz skłaniam się ku czystszemu sposobowi używania zmiennej globalnej dla singletonów.

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

Jak wspomniano w artykule na blogu Swift tutaj :

Leniwy inicjator zmiennej globalnej (także dla statycznych elementów struktur i wyliczeń) jest uruchamiany przy pierwszym dostępie do globu i uruchamiany jako dispatch_once, aby upewnić się, że inicjalizacja jest atomowa. Umożliwia to fajny sposób użycia dispatch_once w kodzie: wystarczy zadeklarować zmienną globalną za pomocą inicjatora i oznaczyć ją jako prywatną.

Ten sposób tworzenia singletonów jest bezpieczny, szybki, leniwy, a także połączony z ObjC za darmo.


2
Każdy, kto czyta tylko tę odpowiedź: Pamiętaj, aby token był statyczny, w przeciwnym razie zachowanie nie zostanie zdefiniowane. Zobacz edytowane pytanie Davida dla pełnego kodu.
nschum

@ nschum w przeciwnym razie zachowanie nie jest niezdefiniowane, jest po prostu zepsute w dobrze zdefiniowany sposób: blok zawsze będzie wykonywany.
Michael

@Michael: Dokumentacja stwierdza, że ​​jest niezdefiniowana. Obecne zachowanie jest zatem przypadkowe.
nschum

1
To dziwna rzecz do powiedzenia. Jeśli dokumentacja nazywa to „niezdefiniowanym”, oznacza to po prostu, że ktokolwiek napisał kod, nie składa żadnych obietnic. Nie ma to nic wspólnego z kodem, który wie, czy zmienna jest statyczna. Oznacza to po prostu, że na bieżące (lub pozorne) zachowanie nie można polegać.
nschum

6
Możesz dodać private init() {}jako inicjator SingletonClass. aby zapobiec wystąpieniu z zewnątrz.
rintaro

46

Swift 1.2 lub nowszy obsługuje teraz zmienne statyczne / stałe w klasach. Możesz więc po prostu użyć stałej statycznej:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

35

Jest lepszy sposób, aby to zrobić. Możesz zadeklarować zmienną globalną w swojej klasie powyżej deklaracji klasy w następujący sposób:

var tpScopeManagerSharedInstance = TPScopeManager()

To po prostu wywołuje domyślny init lub cokolwiek init, a zmienne globalne są dispatch_oncedomyślnie w Swift. Następnie w dowolnej klasie, w której chcesz uzyskać referencję, po prostu wykonaj następujące czynności:

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

Zasadniczo możesz więc pozbyć się całego bloku współdzielonego kodu instancji.


3
Dlaczego „var” i dużo „let”?
Stephan

1
może być let, przetestowałem to tylko z var.
Kris Gellci

Podoba mi się ta odpowiedź, jednak muszę uzyskać do niej dostęp (Singleton) z interfejsu konstruktora. Każdy pomysł, w jaki sposób mogę uzyskać dostęp do tego tpScopeManagerSharedInstance z poziomu IB ?. Dzięki.
Luis Palacios,

To mój ulubiony sposób na singleton. Ma wszystkie zwykłe funkcje (bezpieczeństwo wątków i leniwe tworzenie instancji) i obsługuje bardzo lekką składnię: nie musisz TPScopeManager.sharedInstance.doIt()cały czas pisać , po prostu nazwij swoją klasę TPScopeManagerClass, umieść tę deklarację obok klasy public let TPScopeManager = TPScopeManagerClass(), a podczas używania po prostu napisz TPScopeManager.doIt(). Bardzo czysto!
Alex

Nic nie TPScopeManagerstoi na przeszkodzie, aby stworzyć dodatkowe wystąpienia , dlatego też nie jest to singleton z definicji.
Caleb,

28

Singletons Swift są wystawione w ramach kakao jak funkcje klasy, na przykład NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(). Dlatego bardziej sensowne jest, aby funkcja klasy odzwierciedlała to zachowanie, niż zmienna klasowa, jak niektóre inne rozwiązania. na przykład:

class MyClass {

    private static let _sharedInstance = MyClass()

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

Odzyskaj singletona przez MyClass.sharedInstance().


1
głosowano za komentarzem LearnCocos2D :), także za styl.
x4h1d

2
zmienna globalna powinna zostać zmieniona na zmienną klasową poprzez statyczny element wewnątrz klasy.
malhal

2
@malhal, kiedy zmienna jest oznaczona jako prywatna, ale poza klasą, nie jest globalna - ale ma zasięg tylko do pliku, w którym się znajduje. Statyczny wewnątrz klasy działałby prawie tak samo, ale zaktualizowałem odpowiedź, aby użyć statycznego jak zasugerowałeś, ponieważ lepiej grupuje zmienną do klasy, jeśli akurat używasz wielu klas w pliku.
Ryan

1
„Swift Singletony są eksponowane w strukturach kakao jako funkcje klasowe” ... Nie w Swift 3. Są teraz zwykle staticwłaściwościami.
Rob

17

Zgodnie z dokumentacją Apple wiele razy powtarzano, że najłatwiejszym sposobem na to w Swift jest właściwość typu statycznego:

class Singleton {
    static let sharedInstance = Singleton()
}

Jeśli jednak szukasz sposobu na wykonanie dodatkowej konfiguracji poza prostym wywołaniem konstruktora, sekret polega na użyciu natychmiast wywołanego zamknięcia:

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

Gwarantuje to, że jest bezpieczny dla wątków i leniwie inicjowany tylko raz.


Jak ustawić statyczną instancję let z powrotem na zero?
gpichler

1
@ user1463853 - Nie możesz i ogólnie nie powinieneś.
Rob

16

Swift 4+

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

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

2
to wymaga ostatniej klasy, czy możesz wyjaśnić więcej różnicę, bo mam problem z innym rozwiązaniem singletona ze struct
Raheel Sadiq

powinno to być prywatne przesłonięcie init () {}
NSRover

8

Patrząc na przykładowy kod Apple, natknąłem się na ten wzorzec. Nie jestem pewien, jak Swift radzi sobie ze statyką, ale byłoby to bezpieczne dla wątków w C #. Podaję zarówno właściwość, jak i metodę dla współdziałania Objective-C.

struct StaticRank {
    static let shared = RankMapping()
}

class func sharedInstance() -> RankMapping {
    return StaticRank.shared
}

class var shared:RankMapping {
    return StaticRank.shared
}

Jestem pewien, że użycie tej domyślnej składni statycznej wykona wszystkie irytujące zadania.
Eonil

niestety statyka działa tylko wewnątrz struktur, dlatego właśnie ten wzór.
user2485100

Moim zamiarem było, abyśmy nie musieli używać rzeczy dispatch_once. Stawiam na twój styl. :)
Eonil

Czy classw deklaracji klasowej nie ma odpowiednika staticdeklaracji strukturalnej?
Russell Borogove

@Sam Tak to jest. Zobacz wpis na blogu Apple dotyczący plików i inicjalizacji, który wyjaśnia, że ​​zarówno globalne, jak i statyczne elementy struktur i wyliczeń korzystają z tej dispatch_oncemożliwości.
Rob

5

W skrócie,

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

Możesz przeczytać Pliki i inicjowanie

Leniwy inicjator dla zmiennej globalnej (także dla statycznych elementów struktur i wyliczeń) jest uruchamiany przy pierwszym dostępie do globalnej i jest uruchamiany, dispatch_onceaby upewnić się, że inicjalizacja jest atomowa.


4

Jeśli planujesz używać swojej klasy singleton Swift w Objective-C, to ustawienie spowoduje, że kompilator wygeneruje odpowiednie nagłówki podobne do Objective-C:

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

Następnie w klasie Objective-C możesz nazwać singletona tak, jak to robiłeś w dniach sprzed Swift:

[ImageStore sharedStore];

To tylko moja prosta implementacja.


Jest to w rzeczywistości bardziej zwięzłe i poprawne niż w innym przykładzie, ponieważ jest implementowane w taki sam sposób, jak inne pojedyncze singlety Swift. tj .: jak klasa działa podobnie NSFileManager.defaultManager(), ale nadal używa leniwych, bezpiecznych dla wątków mechanizmów statycznych elementów Swift.
Leslie Godwin,

Kakao zwykle implementuje je jako właściwości statyczne, a nie jako funkcje klasowe.
Rob

Jestem tego świadomy, mój komentarz ma ponad 2 lata. Dzięki za wzmiankę.
Michael

4

Pierwsze rozwiązanie

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Później w kodzie:

func someFunction() {        
    var socketManager = SocketManager        
}

Drugie rozwiązanie

func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

A później w kodzie będziesz mógł przechowywać nawiasy klamrowe dla mniejszego zamieszania:

func someFunction() {        
    var socketManager = SocketManager()        
}

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

Nazwij to;

let shared = MySingleton.shared

Dobra robota nie tylko do oznaczania initjako private, ale także do tworzenia sharedMyModeljako final! Ze względu na przyszłych czytelników w Swift 3 możemy chcieć zmienić nazwę sharedMyModelna zwykłą shared.
Rob

Jest to jedyna poprawna odpowiedź, z wyjątkiem tego, że zastąpienie i wywołanie super.init są błędne i nawet się nie skompilują.
Michael Morris

4

Posługiwać się:

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!
        }
    }
}

Jak używać:

UtilSingleton.shareInstance.iVal++
println("singleton new iVal = \(UtilSingleton.shareInstance.iVal)")

Jest to dokładnie to samo, co jedna z odpowiedzi, które przeszedłem w drodze do obecnej odpowiedzi. Ponieważ zmienne globalne są inicjowane zarówno leniwie, jak i są bezpieczne dla wątków, nie ma powodu do dodatkowej złożoności.
David Berry

@David Inne niż brak zmiennej globalnej. :)
hpique

@ hpique nie, dokładnie jak jedna z moich wcześniejszych prób. Spójrz na historię edycji.
David Berry,

4

Najlepszym podejściem w Swift powyżej 1.2 jest singleton jednowierszowy, ponieważ -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

Aby dowiedzieć się więcej o tym podejściu, możesz odwiedzić ten link .


Dlaczego NSObjectpodklasa ?. Poza tym wydaje się, że jest to zasadniczo to samo, co stackoverflow.com/a/28436202/1187415 .
Martin R

3

Z Apple Docs (Swift 3.0.1),

Możesz po prostu użyć właściwości typu statycznego, która jest gwarantowana leniwie zainicjowana tylko raz, nawet jeśli jest dostępna w wielu wątkach jednocześnie:

class Singleton {
    static let sharedInstance = Singleton()
}

Jeśli musisz wykonać dodatkową konfigurację poza inicjalizacją, możesz przypisać wynik wywołania zamknięcia do stałej globalnej:

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

3

Sugerowałbym enum, jak byście używali w Javie, np

enum SharedTPScopeManager: TPScopeManager {
    case Singleton
}

IMO, jest to jedyny prawidłowy szybki sposób na wdrożenie Singletona. inne odpowiedzi to ObjC / C / C ++
Bryan Chen

Czy mógłbyś rozwinąć tę odpowiedź? Nie jest dla mnie jasne, gdzie utworzono instancję Singleton z tego fragmentu
Kenny Winker

@KennyWinker Nie mam loginu programisty Apple, dlatego nie jestem szybki, więc nie mogę odpowiedzieć, gdy nastąpi inicjalizacja. W Javie jest to pierwsze użycie. Być może mógłbyś wypróbować go z drukiem przy inicjalizacji i sprawdzić, czy druk wystąpi przy uruchomieniu, czy po uzyskaniu dostępu. Będzie to zależeć od sposobu, w jaki kompilator implementuje wyliczanie.
Howard Lovatt

@KennyWinkler: Apple właśnie wyjaśniło, jak to działa, patrz developer.apple.com/swift/blog/?id=7 . Mówią w nim: „uruchom inicjalizator dla globalnego przy pierwszym odwołaniu, podobnie jak w Javie”, w szczególności. Mówią także, że pod przykrywkami używają „dispatch_once, aby upewnić się, że inicjalizacja jest atomowa”. Dlatego wyliczanie jest prawie na pewno właściwą drogą, chyba że masz jakiś fantazyjny inicjator, wtedy prywatnym statycznym letem jest rozwiązaniem.
Howard Lovatt

2

Dla przykładu, oto przykładowa implementacja Nested Struct Jacka Wu / hpique Singleton. Implementacja pokazuje również, jak może działać archiwizacja, a także niektóre funkcje towarzyszące. Nie mogłem znaleźć tego pełnego przykładu, więc mam nadzieję, że to komuś pomoże!

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

A jeśli nie rozpoznałeś niektórych z tych funkcji, oto mały, żywy plik narzędzia Swift, którego używałem:

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

W trybie szybkiego możesz utworzyć klasę singleton w następujący sposób:

class AppSingleton: NSObject {

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

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

1

Wolę tę implementację:

class APIClient {

}

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

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

1

Mój sposób wdrożenia w 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")   

}

Uzyskaj dostęp do globalDic z dowolnego ekranu aplikacji poniżej.

Czytać:

 println(ConfigurationManager.sharedInstance.globalDic)  

Pisać:

 ConfigurationManager.sharedInstance.globalDic = tmpDic // tmpDict is any value that to be shared among the application

1

Jedyne właściwe podejście znajduje się poniżej.

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

    private init() {}
}

Mieć dostęp

let signleton = Singleton.sharedInstance

Powody:

  • static gwarantuje się, że właściwość type zostanie leniwie zainicjowana tylko raz, nawet jeśli jest dostępna dla wielu wątków jednocześnie, więc nie trzeba jej używać dispatch_once
  • Prywatyzacja initmetody, więc instancja nie może zostać utworzona przez inne klasy.
  • final klasa, ponieważ nie chcesz, aby inne klasy dziedziczyły klasę Singleton.

Dlaczego static let sharedInstance = Singleton()
użyłeś

1
jeśli nie chcesz wykonywać żadnych dodatkowych ustawień, to co mówisz, jest słuszne.
applefreak

1

Po zobaczeniu implementacji Davida wydaje się, że nie ma potrzeby używania funkcji klasy singleton, instanceMethodponieważ letrobi ona prawie to samo, co sharedInstancemetoda klasowa. Wszystko, co musisz zrobić, to zadeklarować jako globalną stałą i to by było na tyle.

let gScopeManagerSharedInstance = ScopeManager()

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

2
Jak mówię w moich komentarzach, jedynym powodem, aby to zrobić, jest to, że w pewnym momencie w przyszłości możesz przenieść / ukryć zmienną globalną i uzyskać więcej zachowań podobnych do singletona. W tym momencie, jeśli wszystko używa spójnego wzorca, możesz po prostu zmienić same klasy singletonów bez konieczności zmiany użycia.
David Berry,

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!
}

Jak już tu obszernie omówiono, nie jest konieczne szybkie zapakowanie inicjalizacji, dispatch_onceponieważ inicjalizacja zmiennych statycznych jest leniwa i automatycznie chroniona przez dispatch_once Apple, z tego powodu zaleca używanie statyki zamiast dispatch_once.
David Berry

0

Szybka realizacja singletonu w przeszłości to nic innego jak trzy sposoby: zmienne globalne, zmienne wewnętrzne i sposoby dispatch_once.

Oto dwa dobre singletony. (Uwaga: bez względu na to, jaki rodzaj pisania będzie musiał zwrócić uwagę na metodę inicjalizacji metody init (), ponieważ w Swift wszystkie ustawienia domyślne konstruktora obiektu są publiczne, należy zmienić przepis na init, który można przekształcić w prywatny , zapobiegaj domyślnym metodom inicjowania innych obiektów tej klasy „()”, aby utworzyć obiekt).

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

Metoda 2:

class AppManager {
    static let sharedInstance = AppManager()

    private init() {} // Privatizing the init method
}

// How to use?
AppManager.sharedInstance

-1

Jest to najprostszy z zabezpieczonymi wątkami. Żaden inny wątek nie może uzyskać dostępu do tego samego obiektu singleton, nawet jeśli tego chcą. Szybki 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
Jaka jest zaleta w porównaniu z właściwością typu statycznego (która jest gwarantowana leniwie zainicjowana tylko raz, nawet jeśli jest dostępna w wielu wątkach jednocześnie)?
Martin R

-1

Wymagałem, aby mój singleton zezwalał na dziedziczenie i żadne z tych rozwiązań nie pozwalało na to. Więc wpadłem na to:

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
    }
}
  • W ten sposób, robiąc Singleton.sharedInstance()pierwszy, zwróci instancjęSingleton
  • Robiąc SubSingleton.sharedInstance()pierwszy zwróci instancję SubSingletonutworzonej.
  • Jeśli powyższe zostanie wykonane, wówczas SubSingleton.sharedInstance()jest Singletonto prawda i używana jest ta sama instancja.

Problem z tym pierwszym nieprzyzwoitym podejściem polega na tym, że nie mogę zagwarantować, że podklasy zaimplementują dispatch_once_ti upewnią się, że sharedInstanceVarzostanie zmodyfikowany tylko raz na klasę.

Spróbuję to doprecyzować, ale byłoby interesujące sprawdzić, czy ktoś ma do tego silne uczucia (poza tym, że jest pełny i wymaga ręcznej aktualizacji).



-2

Używam następującej składni:

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

Działa to od Swift 1.2 do 4 i ma kilka zalet:

  1. Przypomina użytkownikowi, aby nie implementował podklasy
  2. Zapobiega tworzeniu dodatkowych instancji
  3. Zapewnia leniwe tworzenie i wyjątkową instancję
  4. Skraca składnię (omija ()), umożliwiając dostęp do instancji jako Singleton.instance
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.