ใช้แบบจำลอง dispatch_once ซิงเกิลตันใน Swift


575

ฉันกำลังพยายามหาโมเดลซิงเกิลที่เหมาะสมสำหรับการใช้งานใน 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' เป็น type '()'

ฉันได้ลองใช้รูปแบบที่แตกต่างหลากหลายของไวยากรณ์ แต่พวกเขาดูเหมือนจะมีผลลัพธ์เดียวกัน:

dispatch_once(Static.token, { Static.instance = TPScopeManager() })

การdispatch_onceใช้ Swift ที่เหมาะสมคืออะไร ตอนแรกฉันคิดว่าปัญหาเกิดขึ้นกับบล็อคเนื่องจาก()ในข้อความแสดงข้อผิดพลาด แต่ยิ่งฉันมองมันมากเท่าไรฉันก็ยิ่งคิดว่ามันอาจเป็นเรื่องของการได้รับการdispatch_once_tกำหนดอย่างถูกต้อง


3
ฉันจะลบรหัสคงที่ทั้งหมดและใช้คุณสมบัติแบบอ่านอย่างเดียวพร้อมกับ initializer @lazy
Sulthan

1
นั่นคือสิ่งที่ฉันหมายถึง. น่าเสียดายที่เรายังมีข้อมูลไม่เพียงพอเกี่ยวกับ internals อย่างไรก็ตาม IMHO การดำเนินการใด ๆ ของ@lazyควรปลอดภัยเธรด
Sulthan

1
และวิธีนี้ยังมีข้อได้เปรียบของการไม่เปิดเผยการใช้งานกับการปล้นสะดมของผู้โทร
David Berry

1
ดูเหมือนว่าคุณจะไม่สามารถมีตัวแปรคลาส @lazy
David Berry

ระวัง! สองสิ่งที่ควรทราบด้วยวิธีการนี้ ขั้นแรกคลาสใด ๆ ที่สืบทอดจากนี้จะต้องแทนที่คุณสมบัติ sharedInstance Static.instance = TPScopeManager()กองกำลังประเภทอินสแตนซ์ หากคุณใช้บางอย่างเช่นStatic.instance = self()กับ initializer ที่ต้องการคลาสประเภทที่เหมาะสมจะถูกสร้างขึ้น ถึงกระนั้นและนี่คือสิ่งสำคัญที่ควรทราบเพียงครั้งเดียวสำหรับอินสแตนซ์ทั้งหมดในลำดับชั้น! First type to initialize เป็นประเภทที่กำหนดไว้สำหรับทุกอินสแตนซ์ ฉันไม่คิดว่าวัตถุประสงค์ -c ทำตัวเหมือนกัน
ฌอนวู้ดเวิร์ด

คำตอบ:


713

tl; dr: ใช้วิธีการคงที่คลาสหากคุณใช้ Swift 1.2 หรือสูงกว่าและวิธีการซ้อนแบบซ้อนหากคุณต้องการสนับสนุนเวอร์ชันก่อนหน้า

จากประสบการณ์ของฉันกับ Swift มีสามวิธีในการใช้รูปแบบซิงเกิลตันที่รองรับการเริ่มต้นขี้เกียจและความปลอดภัย

คลาสคงที่

class Singleton  {
   static let sharedInstance = Singleton()
}

วิธีการนี้จะสนับสนุนการเริ่มต้นขี้เกียจเพราะสวิฟท์ซมต้นคงระดับ (และตัวแปร) 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นี้สำหรับการทดสอบหน่วย


13
"ด้ายปลอดภัยโดยอาศัยการปล่อย" - สิ่งนี้ถูกระบุไว้ที่ใด? ฉันไม่สามารถพูดถึงมันได้ในเอกสารประกอบ
jtbandes

4
@ jtbandes ค่าคงที่เป็นเธรดที่ปลอดภัยในทุกภาษาที่ฉันรู้จัก
hpique

2
@DaveWood ฉันคิดว่าคุณกำลังพูดถึงวิธีการสุดท้าย ฉันจะพูดกับตัวเอง: "ฉันจะบอกว่าไม่จำเป็นต้องใช้วิธีการนี้อีกต่อไป แต่ฉันจะวางมันไว้ที่นี่เพราะฉันพบความแตกต่างของไวยากรณ์ที่น่าสนใจ"
hpique

5
ควรinitมีการประกาศprivateเพื่อรับประกันว่ามีเพียงหนึ่งอินสแตนซ์ของวัตถุเท่านั้นที่จะมีอยู่ตลอดช่วงอายุของแอป
Andrew

5
ในแนวทาง "คลาสคงที่" ฉันขอแนะนำ (a) ประกาศคลาสให้เป็นfinalดังนั้นคุณจะไม่ซับคลาส และ (b) ประกาศinitวิธีการเพื่อprivateให้คุณไม่สามารถยกตัวอย่างอินสแตนซ์อื่นโดยไม่ตั้งใจได้
Rob

175

เนื่องจาก Apple ได้ชี้แจงว่าตัวแปร struct แบบคงที่นั้นเริ่มต้นได้ทั้งสันหลังยาวและล้อมรอบdispatch_once(ดูหมายเหตุท้ายโพสต์) ฉันคิดว่าคำตอบสุดท้ายของฉันจะเป็น:

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

        return Singleton.instance
    }
}

สิ่งนี้ใช้ประโยชน์จากการเริ่มต้นอัตโนมัติแบบขี้เกียจและปลอดภัยขององค์ประกอบอิลิเมนต์โครงสร้างแบบคงที่ซึ่งซ่อนการดำเนินการจริงจากผู้บริโภคอย่างปลอดภัยเก็บทุกอย่างไว้ในช่องว่างเล็ก ๆ เพื่อความชัดเจนและกำจัดตัวแปรส่วนกลางที่มองเห็นได้

Apple ได้ชี้แจงว่าผู้เริ่มต้นที่ขี้เกียจมีความปลอดภัยต่อเธรดจึงไม่จำเป็นต้องมีdispatch_onceหรือต้องการการปกป้องที่คล้ายกัน

lazy initializer สำหรับตัวแปรโกลบอล (เช่นสำหรับสมาชิกแบบคงที่ของ struct และ enums) ถูกเรียกใช้เป็นครั้งแรกที่มีการเข้าถึงโกลบอลและถูกเรียกใช้เป็น dispatch_once เพื่อให้แน่ใจว่าการเริ่มต้นนั้นเป็นอะตอมมิก สิ่งนี้ช่วยให้วิธีที่ยอดเยี่ยมในการใช้ dispatch_once ในรหัสของคุณ: เพียงประกาศตัวแปรทั่วโลกด้วยการเริ่มต้นและทำเครื่องหมายเป็นส่วนตัว

จากที่นี่


1
เพื่อยืนยัน: ตัวแปรโกลบอลมีค่าเริ่มต้นขี้เกียจการกำหนดค่าเริ่มต้นที่ปลอดภัยสำหรับเธรด แต่ตัวแปรคลาสไม่มี ขวา?
Bill

14
ฉันจะเพิ่มว่าแนวปฏิบัติที่ดีคือการประกาศให้ initializer เป็นส่วนตัว: private init() {}เพื่อบังคับใช้ความจริงที่ว่าคลาสนี้ไม่ได้หมายถึงการถูกสร้างอินสแตนซ์จากภายนอก
Pascal Bourque

1
การเริ่มต้นแบบคงที่ของ var แบบคงที่คือสันหลังยาวและเธรดที่ปลอดภัย, ถ้าแบบคงที่ var แบบคงที่นั้นเป็นพจนานุกรมสำหรับมัลติแทร็ก, จากนั้นเราต้องซิงโครไนซ์ / คิวการโทรด้วยตนเองสำหรับการเข้าถึงแต่ละครั้งใช่ไหม?

หากฉันเข้าใจคำถามของคุณอย่างถูกต้องการเข้าถึงพจนานุกรมและอาเรย์นั้นไม่ได้ปลอดภัยสำหรับเธรดดังนั้นคุณจะต้องใช้การซิงโครไนซ์เธรดบางรูปแบบ
David Berry

@DavidBerry ฉันจะเรียกใช้ฟังก์ชันภายในคลาสซิงเกิลนี้ได้อย่างไร ฉันต้องการฟังก์ชั่นในการโทรครั้งแรกของ myClass.sharedInstance
Ameet Dhas

163

สำหรับ Swift 1.2 และสูงกว่า:

class Singleton  {
   static let sharedInstance = Singleton()
}

ด้วยหลักฐานความถูกต้อง (เครดิตทั้งหมดเข้าที่นี่ ) ตอนนี้ไม่มีเหตุผลที่จะใช้วิธีการใด ๆ ก่อนหน้านี้สำหรับซิงเกิล

อัปเดต : นี่เป็นวิธีที่เป็นทางการในการกำหนดซิงเกิลตันตามที่อธิบายไว้ในเอกสารอย่างเป็นทางการ !

ในฐานะที่เป็นความกังวลเกี่ยวกับการใช้VSstatic ควรเป็นอันที่ใช้แม้ในขณะที่ตัวแปรพร้อมใช้งาน Singletons ไม่ได้หมายถึง subclassed เนื่องจากจะส่งผลให้เกิด single instance หลายฐาน การใช้บังคับใช้สิ่งนี้ในวิธีที่สวยงามและรวดเร็วclassstaticclassstatic

สำหรับ Swift 1.0 และ 1.1:

ด้วยการเปลี่ยนแปลงล่าสุดใน Swift ซึ่งเป็นวิธีการควบคุมการเข้าถึงส่วนใหญ่ฉันได้เรียนรู้วิธีการใช้ตัวแปรระดับโลกสำหรับซิงเกิล

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

ดังที่ได้กล่าวไว้ในบทความบล็อกของ Swift ที่นี่ :

lazy initializer สำหรับตัวแปรโกลบอล (เช่นสำหรับสมาชิกแบบคงที่ของ struct และ enums) ถูกเรียกใช้เป็นครั้งแรกที่มีการเข้าถึงโกลบอลและถูกเรียกใช้เป็น dispatch_once เพื่อให้แน่ใจว่าการเริ่มต้นนั้นเป็นอะตอมมิก สิ่งนี้ช่วยให้วิธีที่ยอดเยี่ยมในการใช้ dispatch_once ในรหัสของคุณ: เพียงประกาศตัวแปรทั่วโลกด้วยการเริ่มต้นและทำเครื่องหมายเป็นส่วนตัว

วิธีการสร้างซิงเกิลตันนี้ปลอดภัยสำหรับเธรดเร็วขี้เกียจและเชื่อมต่อกับ ObjC ฟรี


2
ใครก็ตามที่อ่านเพียงคำตอบนี้: อย่าลืมทำให้โทเค็นคงที่มิฉะนั้นพฤติกรรมจะไม่ได้กำหนด ดูคำถามที่แก้ไขโดย David สำหรับรหัสที่สมบูรณ์
nschum

@nschum ไม่เช่นนั้นพฤติกรรมจะไม่ได้ถูกกำหนด แต่จะแตกในลักษณะที่กำหนดไว้อย่างดี: บล็อกจะดำเนินการเสมอ
Michael

@Michael: เอกสารระบุว่ายังไม่ได้กำหนด พฤติกรรมปัจจุบันจึงเป็นเรื่องบังเอิญ
nschum

1
นั่นเป็นเรื่องแปลกที่จะพูด หากเอกสารเรียกว่า "ไม่ได้กำหนด" นั่นหมายถึงว่าใครก็ตามที่เขียนรหัสไม่ได้ให้สัญญากับสิ่งที่ทำ มันไม่เกี่ยวอะไรกับโค้ดที่รู้ว่าตัวแปรนั้นเป็นค่าคงที่หรือไม่ หมายความว่าพฤติกรรมปัจจุบัน (หรือชัดเจน) ไม่สามารถพึ่งพาได้
nschum

6
คุณอาจต้องการที่จะเพิ่มprivate init() {}เป็น initialiser SingletonClassของ เพื่อป้องกันอินสแตนซ์จากภายนอก
rintaro

46

Swift 1.2 หรือใหม่กว่ารองรับตัวแปร / ค่าคงที่ในคลาส ดังนั้นคุณสามารถใช้ค่าคงที่:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

35

มีวิธีที่ดีกว่าที่จะทำมัน คุณสามารถประกาศตัวแปรโกลบอลในคลาสของคุณเหนือการประกาศคลาสเช่นนี้:

var tpScopeManagerSharedInstance = TPScopeManager()

สิ่งนี้จะเรียกใช้ค่าเริ่มต้นของคุณหรือค่าเริ่มต้นใดและตัวแปรทั่วโลกเป็นdispatch_onceค่าเริ่มต้นใน Swift จากนั้นในชั้นเรียนใดที่คุณต้องการรับการอ้างอิงคุณเพียงแค่ทำสิ่งนี้:

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

ดังนั้นโดยทั่วไปคุณสามารถกำจัดบล็อคอินสแตนซ์ที่แชร์ทั้งหมดได้


3
ทำไม "var" และ "ให้" มาก?
เตฟาน

1
อาจจะเป็นให้ฉันทดสอบเพียงกับ var
Kris Gellci

ฉันชอบคำตอบนี้ แต่ฉันต้องเข้าถึง (Singleton) จาก Interface Builder มีแนวคิดใดบ้างที่ฉันสามารถเข้าถึง tpScopeManagerSharedInstance จากภายใน IB ได้ไหม ขอบคุณ.
Luis Palacios

นี่คือวิธีที่ฉันต้องการที่จะมีซิงเกิล มันมีคุณสมบัติทั้งหมดตามปกติ (ด้ายความปลอดภัยและการเริ่มขี้เกียจ) และจะสนับสนุนไวยากรณ์ที่มีน้ำหนักเบามาก: ไม่จำเป็นต้องเขียนTPScopeManager.sharedInstance.doIt()ตลอดเวลาเพียงแค่ชื่อชั้นเรียนของคุณTPScopeManagerClassมีการประกาศต่อไปนี้ในชั้นเรียนและเมื่อใช้เพียงเขียนpublic let TPScopeManager = TPScopeManagerClass() TPScopeManager.doIt()สะอาดมาก!
Alex

ไม่มีอะไรที่นี่เพื่อป้องกันการสร้างอินสแตนซ์เพิ่มเติมTPScopeManagerและดังนั้นจึงไม่ใช่คำจำกัดความเดียว
Caleb

28

Swift singletons ถูกเปิดเผยในกรอบ Cocoa เป็นฟังก์ชันคลาสเช่นNSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(). ดังนั้นจึงเหมาะสมกว่าในฐานะฟังก์ชันคลาสเพื่อสะท้อนลักษณะการทำงานนี้แทนที่จะเป็นตัวแปรคลาสเป็นโซลูชันอื่น ๆ เช่น:

class MyClass {

    private static let _sharedInstance = MyClass()

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

MyClass.sharedInstance()เรียกเดี่ยวผ่าน


1
upvoted สำหรับความคิดเห็นของ LearnCocos2D :) เช่นกันสำหรับสไตล์
x4h1d

2
ตัวแปรโกลบอลควรเปลี่ยนเป็นตัวแปรคลาสผ่านสแตติกภายในคลาส
malhal

2
@malhal เมื่อตัวแปรถูกทำเครื่องหมายว่าเป็นส่วนตัว แต่อยู่นอกคลาสมันไม่ได้เป็นแบบโกลบอล - แต่กำหนดขอบเขตเฉพาะไฟล์ที่อยู่ในนั้นคงที่ในชั้นเรียนจะทำงานเหมือนกันมาก แต่ฉันได้ปรับปรุงคำตอบเพื่อใช้คงที่ ตามที่คุณแนะนำเนื่องจากจะจัดกลุ่มตัวแปรไปยังคลาสหากคุณใช้หลายคลาสภายในไฟล์
Ryan

1
"Swift Singletons ถูกเปิดเผยในกรอบของโกโก้ในรูปแบบฟังก์ชันคลาส" ... ไม่ได้อยู่ใน Swift 3 ตอนนี้พวกเขามักจะมีstaticคุณสมบัติ
Rob

17

ตามเอกสารของ Appleมีการทำซ้ำหลายครั้งว่าวิธีที่ง่ายที่สุดใน Swift คือด้วยคุณสมบัติประเภทคงที่:

class Singleton {
    static let sharedInstance = Singleton()
}

อย่างไรก็ตามหากคุณกำลังมองหาวิธีการตั้งค่าเพิ่มเติมนอกเหนือจากการเรียก Constructor แบบธรรมดาความลับคือการปิดการเรียกใช้ทันที:

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

สิ่งนี้รับประกันได้ว่าปลอดภัยต่อเธรดและเริ่มต้นได้อย่างเกียจคร้านเพียงครั้งเดียว


คุณจะตั้งค่าอินสแตนซ์ให้แบบคงที่กลับไปเป็นศูนย์ได้อย่างไร
gpichler

1
@ user1463853 - คุณทำไม่ได้และโดยทั่วไปไม่ควรทำ
Rob

16

Swift 4+

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

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

2
นี้ต้องการชั้นสุดท้ายคุณสามารถอธิบายความแตกต่างมากขึ้นเพราะฉันมีปัญหากับวิธีแก้ปัญหาอื่นของซิงเกิลที่มีโครงสร้าง
Raheel Sadiq

ควรเป็นแบบ override ส่วนตัว () {}
NSRover

8

ดูโค้ดตัวอย่างของ 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
}

ฉันค่อนข้างมั่นใจว่าเพียงแค่ใช้รูปแบบคงที่เริ่มต้นนี้จะทำงานที่น่ารำคาญทั้งหมด
Eonil

โชคไม่ดีที่สถิตศาสตร์ทำงานเฉพาะภายใน structs เท่านั้นนั่นคือสาเหตุที่รูปแบบนี้
2485100

ความตั้งใจของฉันคือเราไม่ต้องใช้dispatch_onceสิ่งของ ฉันกำลังเดิมพันกับสไตล์ของคุณ :)
Eonil

classการประกาศคลาสไม่เหมือนกับการประกาศstaticใน struct หรือไม่?
รัสเซล Borogove

@Sam ใช่มันเป็น ดูรายการบล็อกของ Apple ในไฟล์และการกำหนดค่าเริ่มต้นซึ่งทำให้ชัดเจนว่าทั้งกลมและสมาชิกคงที่ของ structs และ enums ได้รับประโยชน์จากdispatch_onceความสามารถนี้
Rob

5

โดยสังเขป

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

คุณอาจต้องการอ่านไฟล์และการกำหนดค่าเริ่มต้น

lazy initializer สำหรับตัวแปรโกลบอล (สำหรับสมาชิกแบบคงที่ของ structs และ enums) ถูกเรียกใช้ในครั้งแรกที่เข้าถึงโกลบอลและถูกเรียกใช้dispatch_onceเพื่อให้แน่ใจว่าการกำหนดค่าเริ่มต้นนั้นเป็นอะตอมมิก


4

หากคุณวางแผนที่จะใช้คลาส Swift singleton ของคุณใน Objective-C การตั้งค่านี้จะมีคอมไพเลอร์สร้างส่วนหัวคล้าย C Objective-C ที่เหมาะสม:

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

จากนั้นในคลาส Objective-C คุณสามารถโทรหาซิงเกิลของคุณในแบบที่คุณเคยทำมาก่อนวันสวิฟต์:

[ImageStore sharedStore];

นี่เป็นเพียงการดำเนินการที่เรียบง่ายของฉัน


นี่คือความรัดกุมและถูกต้องมากกว่าตัวอย่างอื่น ๆ เนื่องจากมีการนำไปใช้ในลักษณะเดียวกับ Swton ซิงเกิลอื่น ๆ เช่น: เป็นฟังก์ชั่นชั้นชอบNSFileManager.defaultManager()แต่ยังคงใช้ด้ายปลอดภัยกลไกสมาชิกคงขี้เกียจของสวิฟท์
Leslie Godwin

โกโก้โดยทั่วไปจะใช้สิ่งเหล่านี้เป็นคุณสมบัติคงที่ในปัจจุบันไม่เป็นฟังก์ชั่นระดับ
Rob

ฉันทราบดีว่าความคิดเห็นของฉันมีอายุเกิน 2 ปี ขอบคุณที่พูดถึง
ไมเคิล

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
final class MySingleton {
     private init() {}
     static let shared = MySingleton()
}

จากนั้นเรียกมันว่า

let shared = MySingleton.shared

ทำได้ดีไม่เพียง แต่ทำเครื่องหมายinitเป็นprivateแต่ยังทำให้sharedMyModelเป็นfinal! เพื่อประโยชน์ของผู้อ่านในอนาคตในสวิฟท์ 3 เราอาจจะมีแนวโน้มที่จะเปลี่ยนชื่อเป็นแค่sharedMyModel shared
Rob

นี่เป็นคำตอบเดียวที่ถูกต้องยกเว้นว่าการแทนที่และการเรียกไปที่ super.init นั้นผิดพลาดและจะไม่ได้รวบรวม
Michael Morris

4

ใช้:

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

ตรงกับคำตอบที่ฉันตอบไประหว่างคำตอบปัจจุบัน เนื่องจากตัวแปรทั่วโลกเริ่มต้นได้ทั้งขี้เกียจและปลอดภัยกับเธรดจึงไม่มีเหตุผลสำหรับความซับซ้อนเพิ่มเติม
David Berry

@ David นอกเหนือจากที่ไม่มีตัวแปรโกลบอล :)
hpique

@hpique ไม่เหมือนกับหนึ่งในความพยายามครั้งแรกของฉัน ดูประวัติการแก้ไข
David Berry

4

แนวทางที่ดีที่สุดใน Swift above 1.2 คือ singleton หนึ่งบรรทัดเช่น -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

หากต้องการทราบรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการนี้คุณสามารถเยี่ยมชมลิงค์นี้


ทำไมถึงเป็นNSObjectคลาสย่อย? นอกเหนือจากที่นี้น่าจะเป็นหลักเช่นเดียวกับstackoverflow.com/a/28436202/1187415
Martin R

3

จาก 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
    }()
}

3

ฉันอยากจะแนะนำenumอย่างที่คุณจะใช้ใน Java เช่น

enum SharedTPScopeManager: TPScopeManager {
    case Singleton
}

IMO นี่เป็นวิธีที่รวดเร็วในการปรับใช้ซิงเกิลตัน คำตอบอื่น ๆ มี ObjC / C / C ++ วิธี
ไบรอันเฉิน

คุณช่วยอธิบายคำตอบนี้ได้ไหม? ยังไม่ชัดเจนสำหรับฉันที่ Singleton ถูกสร้างอินสแตนซ์จากตัวอย่างนี้
Kenny Winker

@ KennyWinker ฉันไม่มีการเข้าสู่ระบบของนักพัฒนา Apple ดังนั้นจึงไม่มีความรวดเร็วดังนั้นฉันจึงไม่สามารถตอบได้เมื่อการเริ่มต้นเกิดขึ้น ใน Java มันมีการใช้งานครั้งแรก บางทีคุณอาจลองพิมพ์ด้วยการเริ่มต้นและดูว่าการพิมพ์เกิดขึ้นที่การเปิดตัวหรือหลังการเข้าถึง มันจะขึ้นอยู่กับวิธีการนำ enum ไปใช้งานโดยคอมไพเลอร์
Howard Lovatt

@KennyWinkler: แอปเปิ้ลได้ชี้แจงเพียงแค่วิธีการทำงานนี้ดูdeveloper.apple.com/swift/blog/?id=7 ในนั้นพวกเขาบอกว่า "เรียกใช้ initializer สำหรับทั่วโลกในครั้งแรกที่มีการอ้างอิงคล้ายกับ Java" และโดยเฉพาะอย่างยิ่ง พวกเขายังกล่าวอีกว่าภายใต้ฝาครอบที่พวกเขาใช้ "dispatch_once เพื่อให้แน่ใจว่าการเริ่มต้นเป็นอะตอม" ดังนั้น enum จึงเป็นวิธีที่แน่นอนที่จะไปเว้นแต่ว่าคุณมีผู้เริ่มต้นที่น่าสนใจแล้วการปล่อยให้เป็นส่วนตัวคงเป็นทางออก
Howard Lovatt

2

นี่เป็นตัวอย่างการใช้งาน Singleton ของการใช้งาน Nested Struct ของ Jack Wu / hpique การนำไปใช้ยังแสดงให้เห็นว่าการเก็บถาวรสามารถทำงานได้อย่างไรรวมถึงฟังก์ชั่นบางอย่างที่แนบมาด้วย ฉันไม่สามารถหาตัวอย่างที่สมบูรณ์ได้ดังนั้นหวังว่านี่จะช่วยใครซักคน!

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

ใน swift คุณสามารถสร้างคลาส singleton ได้ดังต่อไปนี้:

class AppSingleton: NSObject {

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

    override init() {
        super.init()
    }
}

1

ฉันชอบการนำไปใช้นี้:

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

แนวทางที่ถูกต้องอยู่ด้านล่าง

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

    private init() {}
}

ในการเข้าถึง

let signleton = Singleton.sharedInstance

เหตุผล:

  • static คุณสมบัติ type รับประกันว่าจะเริ่มต้นได้อย่างเกียจคร้านเพียงครั้งเดียวแม้เมื่อเข้าถึงผ่านหลายเธรดพร้อมกันดังนั้นไม่จำเป็นต้องใช้ dispatch_once
  • การแปรรูปinitวิธีการนั้นไม่สามารถสร้างอินสแตนซ์ของคลาสอื่นได้
  • final คลาสที่คุณไม่ต้องการให้คลาสอื่น ๆ สืบทอดคลาส Singleton

ทำไมคุณถึงใช้การเริ่มต้นการปิดในขณะที่คุณสามารถใช้โดยตรงstatic let sharedInstance = Singleton()
abhimuralidharan

1
หากคุณไม่ต้องการตั้งค่าเพิ่มเติมใด ๆ สิ่งที่คุณพูดนั้นถูกต้อง
applefreak

1

หลังจากเห็นการนำดาวิดมาใช้ดูเหมือนว่าไม่จำเป็นต้องมีฟังก์ชั่นชั้นเดี่ยวinstanceMethodตั้งแต่letทำสิ่งเดียวกับsharedInstanceวิธีการเรียน สิ่งที่คุณต้องทำคือประกาศให้เป็นค่าคงที่ทั่วโลกและมันจะเป็นเช่นนั้น

let gScopeManagerSharedInstance = ScopeManager()

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

2
ดังที่ฉันพูดในความคิดเห็นของฉันเหตุผลเดียวที่ต้องทำคือเมื่อถึงจุดหนึ่งในอนาคตคุณสามารถย้าย / ซ่อนตัวแปรส่วนกลางและรับพฤติกรรมแบบซิงเกิลมากขึ้น ณ จุดนี้หากทุกอย่างใช้รูปแบบที่สอดคล้องกันคุณสามารถเปลี่ยนคลาสเดี่ยวได้โดยไม่ต้องเปลี่ยนการใช้งาน
David Berry

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

ดังที่ได้กล่าวมาแล้วที่นี่มีความยาวมากจึงไม่จำเป็นที่จะต้องทำการเริ่มต้นอย่างรวดเร็วdispatch_onceเนื่องจากการเตรียมใช้งานตัวแปรคงที่นั้นขี้เกียจและได้รับการปกป้องโดยอัตโนมัติผ่านทางdispatch_once Apple แนะนำให้ใช้สถิตยศาสตร์แทน dispatch_once
David Berry

0

รวดเร็วในการตระหนักถึงซิงเกิลตันในอดีตไม่มีอะไรมากไปกว่าสามวิธี: ตัวแปรระดับโลก, ตัวแปรภายใน, และวิธีการกระจายข้อมูล

นี่คือสอง singleton ที่ดี (หมายเหตุ: ไม่ว่าชนิดของการเขียนจะต้องใส่ใจกับ init () วิธีการของความเป็นส่วนตัวเพราะใน Swift ค่าเริ่มต้นของตัวสร้างวัตถุทั้งหมดเป็นแบบสาธารณะ ป้องกันวัตถุอื่นของคลาสนี้ () โดยวิธีการกำหนดค่าเริ่มต้นเพื่อสร้างวัตถุ)

วิธีที่ 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

-1

นี่เป็นสิ่งที่ง่ายที่สุดที่มีความสามารถในการป้องกันเธรด ไม่มีเธรดอื่นสามารถเข้าถึงวัตถุซิงเกิลเดียวกันแม้ว่าพวกเขาต้องการ สวิฟท์ 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!
        }
    }
}

2
ข้อดีของคุณสมบัติประเภทคงที่คืออะไร (ซึ่งรับประกันว่าจะเริ่มต้นได้อย่างเกียจคร้านเพียงครั้งเดียวแม้เมื่อเข้าถึงผ่านหลายเธรดพร้อมกัน)?
Martin R

-1

ฉันต้องการซิงเกิลของฉันเพื่ออนุญาตการสืบทอดและไม่มีวิธีแก้ปัญหาเหล่านี้ที่จริงอนุญาต ดังนั้นฉันมากับสิ่งนี้:

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มีการแก้ไขเพียงครั้งเดียวต่อคลาส

ฉันจะพยายามปรับแต่งสิ่งนี้เพิ่มเติม แต่มันน่าสนใจที่จะดูว่าใครมีความรู้สึกรุนแรงกับเรื่องนี้ (นอกเหนือจากความจริงที่ว่ามันเป็น verbose และจำเป็นต้องปรับปรุงด้วยตนเอง)


-2

นี่คือการดำเนินการของฉัน นอกจากนี้ยังป้องกันโปรแกรมเมอร์จากการสร้างอินสแตนซ์ใหม่:

let TEST = Test()

class Test {

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

private initก็บอกแล้วนี่: stackoverflow.com/a/28436202/1187415
Martin R

-2

ฉันใช้ไวยากรณ์ต่อไปนี้:

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. ลดขนาดไวยากรณ์ (avoids ()) โดยอนุญาตให้เข้าถึงอินสแตนซ์เป็น Singleton.instance
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.