swift單例 - uiview singleton swift




在Swift中使用dispatch_once單例模型 (19)

Swift 1.2中最好的方法是單行單例,

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

要了解更多關於這種方法的細節,你可以訪問這個link

我正在試圖找出一個適合Swift使用的單例模型。 到目前為止,我已經能夠得到一個非線程安全模型,其工作原理如下:

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

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

        return Static.instance!
    }
}

在Static結構中包裝單例實例應該允許單個實例不會與單例實例相衝突而沒有復雜的命名策略,它應該使事情相當私密。 顯然,這個模型並不是線程安全的,所以我嘗試添加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的問題。


Swift 1.2或更高版本現在支持類中的靜態變量/常量。 所以你可以使用一個靜態常量:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

Swift在過去實現單身人士只不過是三種方式:全局變量,內部變量和dispatch_once方式。

(注意:無論什麼樣的寫作都要注意privatisation的init()方法。因為在Swift中,所有對象的構造函數默認都是public的,需要重寫的init可以轉化為private ,通過默認的初始化方法來防止這個類的其他對象'()'創建該對象。)

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

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項目進行單元測試。


對於Swift 1.2及更高版本:

class Singleton  {
   static let sharedInstance = Singleton()
}

有了正確性證明(所有功勞都here ),現在幾乎沒有理由使用任何以前的單身方法。

更新 :這是官方文檔中描述的官方定義單身人士的方式!

至於使用static vs class 。 即使class變量變為可用時, static應該使用。 單身並不意味著被分類,因為這會導致基礎單身的多個實例。 使用static以一種美麗的Swifty方式強制執行此操作。

對於Swift 1.0和1.1:

隨著Swift最近的變化,主要是新的訪問控制方法,我現在傾向於為單身人士使用全局變量的更簡潔的方式。

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

正如Swift博客文章中提到的那樣:

全局變量的惰性初始化器(也適用於結構體和枚舉的靜態成員)在第一次訪問全局時運行,並以dispatch_once的形式啟動以確保初始化是原子性的。 這使得在你的代碼中使用dispatch_once很酷的方式:只需用初始化程序聲明一個全局變量並將其標記為私有。

這種創建單例的方法是線程安全,快速,懶惰,並且也可以免費橋接到ObjC。


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

使用:

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

僅供參考,以下是Jack Wu / hpique的嵌套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()
    }
}

我傾向於使用以下語法作為最完整的語法:

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

我剛剛遇到了這個,但我要求我的單例允許繼承,並且這些解決方案實際上都沒有允許它。

所以我想出了這個:

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是true,並使用相同的實例。

第一個臟方法的問題是,我不能保證子類會實現dispatch_once_t並確保每個類只修改一次sharedInstanceVar ...

我會盡力進一步改進,但看看是否有人對此有強烈的感情(除了冗長的事實,並且需要手動更新它),這將會很有趣。


我更喜歡這個實現:

class APIClient {

}

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

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

我會建議一個枚舉,就像你在Java中使用的一樣,例如:

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

根據Apple文檔 ,它已經重複了很多次,在Swift中這樣做最簡單的方法是使用靜態類型屬性:

class Singleton {
    static let sharedInstance = Singleton()
}

但是,如果您正在尋找一種方法來執行超出簡單構造函數調用的其他設置,秘訣就是使用立即調用的閉包:

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

這是保證線程安全並且只能一次初始化的。


由於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很酷的方式:只需用初始化程序聲明一個全局變量並將其標記為私有。

here


看看蘋果的示例代碼,我遇到了這種模式。 我不確定Swift如何處理靜態,但這在C#中是線程安全的。 我包含Objective-C互操作的屬性和方法。

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

您可能想要閱讀here

全局變量的惰性初始化器(也適用於結構體和枚舉的靜態成員)在第一次訪問全局時運行,並以dispatch_once形式啟動以確保初始化是原子性的。


這是線程安全功能中最簡單的一個。 即使他們想要,其他線程也不能訪問同一個單例對象。 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!
        }
    }
}

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

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






dispatch