web-dev-qa-db-ja.com

Swiftでdispatch_onceシングルトンモデルを使用する

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

Swiftを使ったdispatch_onceの正しい使い方は?私は当初、エラーメッセージの()が原因でブロックに問題があると考えていましたが、よく見ると、dispatch_once_tを正しく定義することが問題になるかもしれないと考えています。

551
David Berry

tl; dr:Swift 1.2以降を使用している場合は クラス定数 アプローチを使用し、以前のバージョンをサポートする必要がある場合は ネスト構造体 アプローチを使用してください。

Swiftでの私の経験から、遅延初期化とスレッドセーフをサポートするシングルトンパターンを実装するための3つのアプローチがあります。

クラス定数

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 project)を参照してください。

692
hpique

Appleは静的構造体変数がlazyとdispatch_onceでラップされて初期化されることを明らかにしたので(私はこの記事の最後にあるメモを参照)、私の最終的な解決策は次のようになると思います。

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

        return Singleton.instance
    }
}

これは、静的なstruct要素の自動的な遅延、スレッドセーフな初期化を利用し、実際の実装を消費者から安全に隠し、読みやすくするためにすべてをコンパートメント化したままにし、目に見えるグローバル変数を排除します。

Appleは、遅延初期化子はスレッドセーフであることを明確にしているので、dispatch_onceや同様の保護は必要ない

グローバル変数用の遅延初期化子(構造体と列挙型の静的メンバ用)も、グローバルが最初にアクセスされたときに実行され、初期化がアトミックであることを確認するためにdispatch_onceとして起動されます。これはあなたのコードの中でdispatch_onceを使うクールな方法を可能にします。イニシャライザでグローバル変数を宣言し、それをprivateとマークするだけです。

ここから

173
David Berry

Swift 1.2以降の場合:

class Singleton  {
   static let sharedInstance = Singleton()
}

正当性の証明(すべての功績が here )になっているので、シングルトンに上記の方法を使用する理由はほとんどないし全くありません。

更新 :これは、 公式ドキュメント で説明されているように、シングルトンを定義する official の方法になりました。

staticclassの使い分けについて。 static変数は、class変数が利用可能になったときでも使用するものであるべきです。シングルトンは、それがベースシングルトンの複数のインスタンスをもたらすので、サブクラス化されることを意味しません。 staticを使用すると、これを美しく、迅速な方法で実施できます。

Swift 1.0および1.1の場合:

Swiftの最近の変更、主に新しいアクセス制御方式のため、シングルトンにグローバル変数を使用するよりクリーンな方法に傾いています。

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

Swiftブログ記事 here で述べたように、

グローバル変数用の遅延初期化子(構造体と列挙型の静的メンバ用)も、グローバルが最初にアクセスされたときに実行され、初期化がアトミックであることを確認するためにdispatch_onceとして起動されます。これはあなたのコードの中でdispatch_onceを使うクールな方法を可能にします。イニシャライザでグローバル変数を宣言し、それをprivateとマークするだけです。

シングルトンを作成するこの方法は、スレッドセーフ、高速、遅延、そしてObjCへの無料ブリッジです。

162
Jack

Swift 1.2以降は、クラス内で静的変数/定数をサポートするようになりました。だから、あなただけの静的定数を使用することができます:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

もっと良い方法があります。このようにクラス宣言より上のクラスでグローバル変数を宣言できます。

var tpScopeManagerSharedInstance = TPScopeManager()

これは単にあなたのデフォルトのinitを呼び出すか、Swiftではinitとグローバル変数のどちらかがデフォルトでdispatch_onceになっているかのどちらかです。次に、どのクラスで参照を取得したい場合でも、次のようにします。

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

だから基本的にあなたは共有インスタンスコードのブロック全体を取り除くことができます。

33
Kris Gellci

Swiftシングルトンは、Cocoaフレームワークではクラス関数として公開されています。 NSFileManager.defaultManager()NSNotificationCenter.defaultCenter()なので、この振る舞いを反映するクラス関数としては、他のソリューションで使用されているクラス変数ではなく、もっと意味があると思います。

class MyClass {

    private static let _sharedInstance = MyClass()

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

MyClass.sharedInstance()を介してシングルトンを取得します。

26
Ryan

Swift 4+

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

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

Appleのドキュメント によると、Swiftでこれを行う最も簡単な方法は静的型プロパティを使うことです。

class Singleton {
    static let sharedInstance = Singleton()
}

しかし、単純なコンストラクタ呼び出しを超えて追加の設定を実行する方法を探しているのであれば、秘訣はすぐに呼び出されるクロージャを使うことです:

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

これはスレッドセーフであることと、一度だけ遅延初期化されることが保証されています。

16
Adrian Macneil

Appleのサンプルコードを見て、私はこのパターンに出くわしました。 Swiftがどのように統計を処理するのかわかりませんが、これはC#ではスレッドセーフです。 Objective-C interopのプロパティとメソッドの両方を含めます。

struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}
8
user2485100

簡単に言えば、

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

ファイルと初期化を読みたいかもしれません

グローバル変数用の遅延初期化子(構造体と列挙型の静的メンバ用)も、グローバルが最初にアクセスされたときに実行され、初期化がアトミックであることを確認するためにdispatch_onceとして起動されます。

5
onmyway133

Objective-CでSwiftシングルトンクラスを使用することを計画している場合は、この設定によってコンパイラは適切なObjective-Cのようなヘッダを生成します。

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

その後、Objective-Cのクラスでは、Swift以前の時代に行った方法でシングルトンを呼び出すことができます。

[ImageStore sharedStore];

これは私の単純な実装です。

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

それを呼び出します。

let shared = MySingleton.shared
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
Nicolas Manzini

つかいます:

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

1.2以上のSwiftでの最善のアプローチは、次のように1行シングルトンです。

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

このアプローチの詳細を知るためには、この link にアクセスしてください。

4
CodeCracker

あなたがJavaで使うのと同じように、私はEnumを提案するでしょう。

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}
3
Howard Lovatt

アップルから ドキュメント (Swift 3.0.1)、

単純に静的型プロパティを使用することができます。これは、複数のスレッドにまたがって同時にアクセスされる場合でも、1回だけ遅延初期化されることが保証されています。

class Singleton {
    static let sharedInstance = Singleton()
}

初期化以外に追加の設定を実行する必要がある場合は、クロージャの呼び出しの結果をグローバル定数に割り当てることができます。

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

唯一の正しいアプローチは以下の通りです

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

    private init() {}
}

アクセスするために

let signleton = Singleton.sharedInstance

理由:

  • 静的型プロパティは、複数のスレッドにまたがって同時にアクセスされる場合でも、1回だけ遅延初期化されることが保証されているので、dispatch_onceを使用する必要はありません
  • Initメソッドを民営化してインスタンスを他のクラスで作成できないようにします。
  • 他のクラスがシングルトンクラスを継承しないようにするための最終クラス
2
applefreak

参考までに、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()
    }
}
2
SchoonSauce

Davidの実装を見た後、letはsharedInstanceクラスメソッドとほとんど同じことをしているので、シングルトンクラス関数instanceMethodを持つ必要はないようです。あなたがする必要があるのはそれを大域定数として宣言することだけであり、それはそれであろう。

let gScopeManagerSharedInstance = ScopeManager()

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

私はこの実装が好きです:

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
user2737730
   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!
}
0
DD.amor

Swiftでは、次のようにしてシングルトンクラスを作成できます。

class AppSingleton: NSObject {

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

    override init() {
        super.init()
    }
}
0
Vicky Prajapati

過去にシングルトンを実現するための迅速なことは、3つの方法、グローバル変数、内部変数、およびdispatch_onceの方法に他なりません。

ここに2つの良いシングルトンがあります。デフォルトの初期化メソッドで、このクラスの他のオブジェクトがオブジェクトを作成するのを防ぎます。)

方法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
0
Tim