باستخدام نموذج singleton dispatch_once في Swift




(19)

أفضل طريقة في سويفت فوق 1.2 هي مفردة أحادية الخط ، كما -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

لمعرفة المزيد من التفاصيل حول هذه المقاربة ، يمكنك زيارة هذا link .

أحاول العمل على نموذج مفرد مناسب للاستخدام في سويفت. حتى الآن ، تمكنت من الحصول على نموذج آمن بخيط لا يعمل كما يلي:

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

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

        return Static.instance!
    }
}

يجب أن يسمح التفاف غلاف singleton في Static struct بمثيل واحد لا يتصادم مع مثيلات فردية بدون مخططات تسمية معقدة ، ويجب أن يجعل الأمور خاصة إلى حد ما. وبالرغم من ذلك ، فإن هذا النموذج ليس خيطًا آمنًا ، لذلك حاولت إضافة 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_once باستخدام سويفت؟ اعتقدت في البداية أن المشكلة كانت مع الكتلة بسبب () في رسالة الخطأ ، ولكن كلما نظرت إليها ، كلما اعتقدت أنها قد تكون مسألة الحصول على dispatch_once_t بشكل صحيح.


أود أن أقترح التعداد ، كما لو كنت تستخدم في Java ، على سبيل المثال:

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}

استعمال:

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

انا افضل هذا التنفيذ:

class APIClient {

}

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

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

بالنظر إلى نموذج عينة Apple ، صادفت هذا النمط. لست متأكدًا من كيفية تعامل Swift مع الإحصاءات ، ولكن هذا سيكون خيطًا آمنًا في C #. أقوم بتضمين كل من الخاصية وطريقة Interop-C interop.

struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}

بعد رؤية تطبيق ديفيد ، يبدو أنه ليس هناك حاجة لوجود مثيل وظيفة singleton classMethod لأن let نفعل نفس الشيء تمامًا مثل طريقة classedInstance. كل ما عليك القيام به هو أن تعلن أنها ثابتة عالمية وسيكون ذلك.

let gScopeManagerSharedInstance = ScopeManager()

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

حسب وثائق Apple ، تم تكرارها عدة مرات أن أسهل طريقة للقيام بذلك في Swift هي خاصية نوع ثابت:

class Singleton {
    static let sharedInstance = Singleton()
}

ومع ذلك ، إذا كنت تبحث عن طريقة لإجراء إعداد إضافي يتعدى مجرد اتصال منشئ بسيط ، فإن السر هو استخدام إغلاق مستدعي على الفور:

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

هذا مضمون لتكون آمنة موضوع مؤشر الترابط وتهيئة lazily مرة واحدة فقط.


سريع لتحقيق المفرد في الماضي ، ليس أكثر من الطرق الثلاث: المتغيرات العالمية ، المتغيرات الداخلية وطرق 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()

الطريقة الثانية:

class AppManager {
    static let sharedInstance = AppManager()

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

// How to use?
AppManager.sharedInstance

لقد توصلت للتو إلى هذا ، لكني طلبت من بلدي المفرد السماح بالميراث ، ولم يسمح به أي من هذه الحلول في الواقع.

لذا توصلت إلى هذا:

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 مرة واحدة فقط لكل فئة ...

سأحاول تحسين هذا الأمر أكثر ، ولكن سيكون من المثير للاهتمام معرفة ما إذا كان لدى أي شخص مشاعر قوية ضد هذا (إلى جانب حقيقة أنه مطوّل ويتطلب تحديثه يدويًا).


للاشارة فقط ، هنا مثال لتنفيذ Singleton لتطبيق Jack N / Jack. كما يوضح التنفيذ كيفية عمل الأرشفة ، بالإضافة إلى بعض الوظائف المصاحبة لها. لم أتمكن من العثور على هذا المثال الكامل ، لذلك نأمل أن يساعد هذا شخصًا ما!

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

هذا هو أبسط واحد مع قدرات آمنة موضوع. لا يمكن لأي مؤشر ترابط آخر الوصول إلى نفس الكائن المفرد حتى إذا أراد. سويفت 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!
        }
    }
}

هذا هو التنفيذ الخاص بي. كما يمنع المبرمج من إنشاء مثيل جديد:

let TEST = Test()

class Test {

    private init() {
        // This is a private (!) constructor
    }
}

يدعم Swift 1.2 أو أحدث الآن المتغيرات / الثوابت الثابتة في الفئات. لذلك يمكنك فقط استخدام ثابت ثابت:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

tl؛ dr: استخدم الأسلوب الثابت للفئة إذا كنت تستخدم Swift 1.2 أو أعلى ونهج البنية المتداخلة إذا كنت تحتاج إلى دعم الإصدارات السابقة.

من تجربتي مع Swift هناك ثلاث طرق لتنفيذ نمط Singleton الذي يدعم التهيئة البطيئة وسلامة الخيط.

ثابت الصف

class Singleton  {
   static let sharedInstance = Singleton()
}

يدعم هذا الأسلوب التهيئة البطيئة لأن Swift يقوم بتهيئة الثوابت الطبقية (والمتغيرات) بكسل ، كما أنه آمن بخيط التعريف. هذه هي الطريقة الموصى بها رسميا الآن لإنشاء مثيل مفرد.

تم تقديم الثوابت الطبقية في 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 التقليدي إلى سويفت. أنا متأكد إلى حد ما من عدم وجود ميزة على نهج البناء المتداخل لكنني أضعه هنا على أي حال حيث أجد الاختلافات في بناء الجملة مثيرة للاهتمام.

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 لاختبارات الوحدة.


سويفت 4+

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

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

ل Swift 1.2 وما بعده:

class Singleton  {
   static let sharedInstance = Singleton()
}

مع وجود دليل على صحة (كل الفضل here ) ، هناك القليل من أي سبب الآن لاستخدام أي من الأساليب السابقة للالأفراد المفردين.

تحديث : هذه هي الطريقة الرسمية الآن لتعريف المفردات كما هو موضح في المستندات الرسمية !

أما بالنسبة للقلق بشأن استخدام class static مقابل. يجب أن يكون static هو الشخص الذي يستخدم حتى عند توفر متغيرات class . ليس من المفترض أن يكون Singletons مصنّفًا تحت فئة فرعية لأن ذلك قد يؤدي إلى حدوث حالات متعددة من المفرد الأساسي. باستخدام static يفرض هذا بطريقة جميلة ، Swifty.

بالنسبة لـ Swift 1.0 و 1.1:

مع التغييرات الأخيرة في سويفت ، ومعظمها أساليب التحكم في الوصول الجديدة ، أميل الآن نحو الطريقة الأنظف لاستخدام متغير عالمي للأحرف الفردية.

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

كما ورد في مقالة Swift blog here :

يتم تشغيل المبدئ البطيئ للمتغير الشامل (أيضًا للأعضاء الاستراتيجيين للبنيات والتعدادات) في المرة الأولى التي يتم فيها الوصول إلى العمومية ، ويتم تشغيله كـ dispatch_once للتأكد من أن التهيئة ذرية. يتيح ذلك طريقة رائعة لاستخدام dispatch_once في التعليمات البرمجية: فقط قم بتعريف متغير عمومي مع مُهيئ وقم بتمييزه بشكل خاص.

هذه الطريقة في إنشاء مفردات هي خيط آمن وسريع وكسول وجسر أيضًا إلى ObjC مجانًا.


أميل إلى استخدام الصيغة التالية باعتبارها الأكثر اكتمالا:

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

هذا يعمل من سويفت 1.2 حتى 4 ، ويقدم العديد من الفضائل:

  1. يذكر المستخدم بعدم تنفيذ الفئة الفرعية
  2. يمنع إنشاء حالات إضافية
  3. يضمن إنشاء بطيء و إنشاء مثيل فريد
  4. تقصير بناء الجملة (يتجنب ()) عن طريق السماح للوصول إلى مثيل Singleton.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