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




(24)

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

class APIClient {

}

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

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

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

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 بشكل صحيح.


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

الحل الأول

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

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

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

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

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

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

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

ثم نسميها.

let shared = MySingleton.shared

طريقي للتنفيذ في سويفت ...

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

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

let TEST = Test()

class Test {

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

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


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

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

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

هناك طريقة أفضل للقيام بذلك. يمكنك أن تعلن عن متغير عام في صفك أعلى من تصنيف الطبقة مثل ذلك

var tpScopeManagerSharedInstance = TPScopeManager()

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

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

حتى في الأساس يمكنك التخلص من كتلة رمز المثيل المشترك.


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

let gScopeManagerSharedInstance = ScopeManager()

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

إذا كنت تخطط لاستخدام فئة مفردات Swift الخاصة بك في Objective-C ، فسيكون لهذا الإعداد المحول البرمجي إنشاء رأس (أو رؤوس) Objective-C مناسبة:

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

ثم في فئة Objective-C يمكنك الاتصال بـ singleton الخاص بك بالطريقة التي قمت بها في أيام ما قبل سويفت:

[ImageStore sharedStore];

هذا هو مجرد تنفيذ بسيط.


منذ أن أوضحت شركة 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


استعمال:

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+

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

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


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

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

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


ل 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 مجانًا.


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

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





swift singleton dispatch