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
を正しく定義することが問題になるかもしれないと考えています。
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以前に静的クラス定数がないための回避策ですが、関数内に静的定数や変数がないための回避策として機能します。
従来の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)を参照してください。
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とマークするだけです。
Swift 1.2以降の場合:
class Singleton {
static let sharedInstance = Singleton()
}
正当性の証明(すべての功績が here )になっているので、シングルトンに上記の方法を使用する理由はほとんどないし全くありません。
更新 :これは、 公式ドキュメント で説明されているように、シングルトンを定義する official の方法になりました。
static
とclass
の使い分けについて。 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への無料ブリッジです。
Swift 1.2以降は、クラス内で静的変数/定数をサポートするようになりました。だから、あなただけの静的定数を使用することができます:
class MySingleton {
static let sharedMySingleton = MySingleton()
private init() {
// ...
}
}
もっと良い方法があります。このようにクラス宣言より上のクラスでグローバル変数を宣言できます。
var tpScopeManagerSharedInstance = TPScopeManager()
これは単にあなたのデフォルトのinitを呼び出すか、Swiftではinitとグローバル変数のどちらかがデフォルトでdispatch_onceになっているかのどちらかです。次に、どのクラスで参照を取得したい場合でも、次のようにします。
var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()
だから基本的にあなたは共有インスタンスコードのブロック全体を取り除くことができます。
Swiftシングルトンは、Cocoaフレームワークではクラス関数として公開されています。 NSFileManager.defaultManager()
、NSNotificationCenter.defaultCenter()
なので、この振る舞いを反映するクラス関数としては、他のソリューションで使用されているクラス変数ではなく、もっと意味があると思います。
class MyClass {
private static let _sharedInstance = MyClass()
class func sharedInstance() -> MyClass {
return _sharedInstance
}
}
MyClass.sharedInstance()
を介してシングルトンを取得します。
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がどのように統計を処理するのかわかりませんが、これはC#ではスレッドセーフです。 Objective-C interopのプロパティとメソッドの両方を含めます。
struct StaticRank {
static let shared = RankMapping()
}
class func sharedInstance() -> RankMapping {
return StaticRank.shared
}
class var shared:RankMapping {
return StaticRank.shared
}
簡単に言えば、
class Manager {
static let sharedInstance = Manager()
private init() {}
}
グローバル変数用の遅延初期化子(構造体と列挙型の静的メンバ用)も、グローバルが最初にアクセスされたときに実行され、初期化がアトミックであることを確認するために
dispatch_once
として起動されます。
Objective-CでSwiftシングルトンクラスを使用することを計画している場合は、この設定によってコンパイラは適切なObjective-Cのようなヘッダを生成します。
class func sharedStore() -> ImageStore {
struct Static {
static let instance : ImageStore = ImageStore()
}
return Static.instance
}
その後、Objective-Cのクラスでは、Swift以前の時代に行った方法でシングルトンを呼び出すことができます。
[ImageStore sharedStore];
これは私の単純な実装です。
final class MySingleton {
private init() {}
static let shared = MySingleton()
}
それを呼び出します。
let shared = MySingleton.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()
}
つかいます:
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)")
1.2以上のSwiftでの最善のアプローチは、次のように1行シングルトンです。
class Shared: NSObject {
static let sharedInstance = Shared()
private override init() { }
}
このアプローチの詳細を知るためには、この link にアクセスしてください。
あなたがJavaで使うのと同じように、私はEnumを提案するでしょう。
enum SharedTPScopeManager: TPScopeManager {
case Singleton
}
アップルから ドキュメント (Swift 3.0.1)、
単純に静的型プロパティを使用することができます。これは、複数のスレッドにまたがって同時にアクセスされる場合でも、1回だけ遅延初期化されることが保証されています。
class Singleton {
static let sharedInstance = Singleton()
}
初期化以外に追加の設定を実行する必要がある場合は、クロージャの呼び出しの結果をグローバル定数に割り当てることができます。
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}
唯一の正しいアプローチは以下の通りです
final class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code if anything
return instance
}()
private init() {}
}
アクセスするために
let signleton = Singleton.sharedInstance
理由:
参考までに、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()
}
}
Davidの実装を見た後、let
はsharedInstanceクラスメソッドとほとんど同じことをしているので、シングルトンクラス関数instanceMethodを持つ必要はないようです。あなたがする必要があるのはそれを大域定数として宣言することだけであり、それはそれであろう。
let gScopeManagerSharedInstance = ScopeManager()
class ScopeManager {
// No need for a class method to return the shared instance. Use the gScopeManagerSharedInstance directly.
}
私はこの実装が好きです:
class APIClient {
}
var sharedAPIClient: APIClient = {
return APIClient()
}()
extension APIClient {
class func sharedClient() -> APIClient {
return sharedAPIClient
}
}
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
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!
}
Swiftでは、次のようにしてシングルトンクラスを作成できます。
class AppSingleton: NSObject {
//Shared instance of class
static let sharedInstance = AppSingleton()
override init() {
super.init()
}
}
過去にシングルトンを実現するための迅速なことは、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