Sử dụng mô hình singleton singleton trong Swift


575

Tôi đang cố gắng tạo ra một mô hình singleton thích hợp để sử dụng trong Swift. Cho đến nay, tôi đã có thể có được một mô hình an toàn không có luồng hoạt động như:

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

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

        return Static.instance!
    }
}

Việc bao bọc cá thể đơn lẻ trong cấu trúc tĩnh sẽ cho phép một cá thể đơn lẻ không va chạm với các cá thể đơn lẻ mà không có các sơ đồ đặt tên phức tạp và nó sẽ làm cho mọi thứ khá riêng tư. Rõ ràng, mặc dù, mô hình này không an toàn chủ đề. Vì vậy, tôi đã cố gắng để thêm dispatch_oncevào toàn bộ:

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

Nhưng tôi gặp lỗi trình biên dịch trên dispatch_oncedòng:

Không thể chuyển đổi loại 'Void' thành loại '()'

Tôi đã thử một số biến thể khác nhau của cú pháp, nhưng tất cả chúng đều có cùng kết quả:

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

Cách sử dụng thích hợp của dispatch_onceviệc sử dụng Swift là gì? Ban đầu tôi nghĩ vấn đề là do khối ()thông báo lỗi, nhưng càng nhìn vào nó, tôi càng nghĩ rằng đó có thể là vấn đề của việc dispatch_once_txác định chính xác.


3
Tôi sẽ xóa tất cả mã tĩnh đó và sử dụng thuộc tính chỉ đọc với trình khởi tạo @lazy.
Sulthan

1
Ý tôi là thế Thật không may, chúng tôi vẫn không có đủ thông tin về nội bộ. Tuy nhiên, IMHO bất kỳ thực hiện @lazynên được chủ đề an toàn.
Sulthan

1
Và cách này cũng có lợi thế là không phơi bày việc thực hiện trước những dự đoán của người gọi.
David Berry

1
Dường như bạn không thể có các biến lớp @lazy.
David Berry

Hãy cẩn thận! Hai điều cần lưu ý với phương pháp này. Đầu tiên, bất kỳ lớp nào kế thừa từ điều này sẽ phải ghi đè lên thuộc tính sharedInstance. Static.instance = TPScopeManager()buộc loại thể hiện. Nếu bạn sử dụng một cái gì đó giống như Static.instance = self()với một trình khởi tạo bắt buộc, lớp loại thích hợp sẽ được tạo. Mặc dù vậy, và đây là điều quan trọng cần lưu ý, chỉ một lần cho tất cả các trường hợp trong hệ thống phân cấp! Loại đầu tiên để khởi tạo là loại được đặt cho tất cả các trường hợp. Tôi không nghĩ khách quan-c cư xử như vậy.
sean woodward

Câu trả lời:


713

tl; dr: Sử dụng cách tiếp cận hằng lớp nếu bạn đang sử dụng Swift 1.2 trở lên và cách tiếp cận cấu trúc lồng nhau nếu bạn cần hỗ trợ các phiên bản trước.

Từ kinh nghiệm của tôi với Swift, có ba cách tiếp cận để triển khai mẫu Singleton hỗ trợ khởi tạo lười biếng và an toàn luồng.

Lớp hằng

class Singleton  {
   static let sharedInstance = Singleton()
}

Cách tiếp cận này hỗ trợ khởi tạo lười biếng vì Swift lười biếng khởi tạo các hằng số lớp (và biến) và là luồng an toàn theo định nghĩa của let. Đây là cách chính thức được đề xuất để khởi tạo một singleton.

Các hằng số lớp được giới thiệu trong Swift 1.2. Nếu bạn cần hỗ trợ phiên bản Swift cũ hơn, hãy sử dụng phương pháp cấu trúc lồng nhau bên dưới hoặc hằng số toàn cầu.

Cấu trúc lồng nhau

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

Ở đây chúng ta đang sử dụng hằng số tĩnh của một cấu trúc lồng nhau như một hằng lớp. Đây là một cách giải quyết cho việc thiếu các hằng lớp tĩnh trong Swift 1.1 trở về trước và vẫn hoạt động như một cách giải quyết cho việc thiếu các hằng và biến tĩnh trong các hàm.

công văn

Cách tiếp cận Objective-C truyền thống được chuyển sang Swift. Tôi khá chắc chắn không có lợi thế so với cách tiếp cận cấu trúc lồng nhau nhưng dù sao tôi cũng đặt nó ở đây vì tôi thấy sự khác biệt trong cú pháp thú vị.

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

Xem dự án GitHub này để kiểm tra đơn vị.


13
"chủ đề an toàn nhờ vào sự cho phép" - điều này đã được nêu ở bất cứ đâu chưa? Tôi không thể tìm thấy đề cập đến nó trong tài liệu.
jtbandes

4
@jtbandes Hằng là chủ đề an toàn trong tất cả các ngôn ngữ tôi biết.
hpique

2
@DaveWood Tôi giả sử bạn đang nói về phương pháp cuối cùng. Tôi sẽ tự trích dẫn: "Tôi sẽ nói rằng không còn cần thiết phải sử dụng phương pháp này nữa nhưng dù sao tôi cũng sẽ đặt nó ở đây vì tôi thấy sự khác biệt trong cú pháp thú vị."
hpique

5
Cũng nên initđược khai báo privateđể đảm bảo một và chỉ một trường hợp của đối tượng sẽ tồn tại trong suốt vòng đời của ứng dụng?
Andrew

5
Trong cách tiếp cận "Hằng số lớp", tôi đề nghị (a) khai báo lớp là finalđể bạn không phân lớp nó; và (b) khai báo initphương thức để privatebạn không thể vô tình khởi tạo một thể hiện khác ở đâu đó.
Rob

175

Vì Apple hiện đã làm rõ rằng các biến cấu trúc tĩnh được khởi tạo cả lười biếng và được bao bọc dispatch_once(xem ghi chú ở cuối bài), tôi nghĩ rằng giải pháp cuối cùng của tôi sẽ là:

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

        return Singleton.instance
    }
}

Điều này tận dụng sự tự động lười biếng, khởi tạo an toàn luồng của các thành phần cấu trúc tĩnh, che giấu việc triển khai thực tế từ người tiêu dùng một cách an toàn, giữ mọi thứ được sắp xếp gọn gàng để dễ đọc và loại bỏ một biến toàn cục có thể nhìn thấy.

Apple đã làm rõ rằng trình khởi chạy lười biếng là an toàn cho luồng, do đó không cần dispatch_oncehoặc bảo vệ tương tự

Bộ khởi tạo lười biếng cho một biến toàn cục (cũng cho các thành viên tĩnh của structs và enums) được chạy lần đầu tiên khi toàn cầu được truy cập và được khởi chạy dưới dạng Clark_once để đảm bảo rằng việc khởi tạo là nguyên tử. Điều này cho phép một cách tuyệt vời để sử dụng Clark_once trong mã của bạn: chỉ cần khai báo một biến toàn cục bằng trình khởi tạo và đánh dấu nó ở chế độ riêng tư.

Từ đây


1
Để xác nhận: các biến toàn cục có khởi tạo lười biếng, an toàn luồng, nhưng các biến lớp thì không. Đúng?
Bill

14
Tôi sẽ nói thêm rằng một thực tiễn tốt sẽ là khai báo trình khởi tạo là riêng tư : private init() {}, để thực thi hơn nữa thực tế là lớp này không có nghĩa là được khởi tạo bên ngoài.
Pascal Bourque

1
Vì vậy, khởi tạo var struct cấu trúc tĩnh là lười biếng và luồng an toàn, nếu var cấu trúc tĩnh đó là một từ điển cho multitons, thì chúng ta phải tự đồng bộ hóa / hàng đợi các cuộc gọi đến nó cho mỗi lần truy cập, phải không?

Nếu tôi hiểu chính xác câu hỏi của bạn, truy cập từ điển và mảng không phải là chủ đề an toàn chủ đề, vì vậy bạn sẽ cần sử dụng một số hình thức đồng bộ hóa luồng.
David Berry

@David BlackBerry Làm thế nào tôi nên gọi một chức năng trong lớp singleton này? Tôi cần một hàm để được gọi trong cuộc gọi đầu tiên của myClass. SharedInstance.
Ameet Dhas

163

Dành cho Swift 1.2 trở lên:

class Singleton  {
   static let sharedInstance = Singleton()
}

Với một bằng chứng về tính chính xác (tất cả tín dụng ở đây ), hiện tại có rất ít lý do để sử dụng bất kỳ phương pháp nào trước đây cho singletons.

Cập nhật : Đây là cách chính thức để xác định singletons như được mô tả trong các tài liệu chính thức !

Đối với những lo ngại về việc sử dụng staticvs class. staticnên là một trong những để sử dụng ngay cả khi classcác biến có sẵn. Singletons không có nghĩa là được phân lớp vì điều đó sẽ dẫn đến nhiều trường hợp của singleton cơ sở. Sử dụngstatic thực thi điều này một cách đẹp đẽ, Swifty.

Đối với Swift 1.0 và 1.1:

Với những thay đổi gần đây trong Swift, chủ yếu là các phương thức kiểm soát truy cập mới, giờ đây tôi đang nghiêng về cách sử dụng biến toàn cục hơn cho singletons.

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

Như đã đề cập trong bài viết trên blog của Swift ở đây :

Bộ khởi tạo lười biếng cho một biến toàn cục (cũng cho các thành viên tĩnh của structs và enums) được chạy lần đầu tiên khi toàn cầu được truy cập và được khởi chạy dưới dạng Clark_once để đảm bảo rằng việc khởi tạo là nguyên tử. Điều này cho phép một cách tuyệt vời để sử dụng Clark_once trong mã của bạn: chỉ cần khai báo một biến toàn cục bằng trình khởi tạo và đánh dấu nó ở chế độ riêng tư.

Cách tạo singleton này là chủ đề an toàn, nhanh chóng, lười biếng và cũng được kết nối miễn phí với ObjC.


2
Bất cứ ai chỉ đọc câu trả lời này: Hãy nhớ làm cho mã thông báo tĩnh, nếu không thì hành vi không được xác định. Xem câu hỏi được chỉnh sửa của David cho mã hoàn chỉnh.
nschum

@nschum nếu không, hành vi không được xác định, nó chỉ bị phá vỡ theo cách được xác định rõ: khối sẽ luôn luôn thực thi.
Michael

@Michael: Tài liệu nêu rõ nó không được xác định. Các hành vi hiện tại là do trùng hợp.
nschum

1
Đó là một điều kỳ lạ để nói. Nếu tài liệu gọi nó là "không xác định", điều đó chỉ có nghĩa là bất cứ ai đã viết mã không thực hiện bất kỳ lời hứa nào với những gì nó làm. Nó không có gì để làm với mã biết nếu biến là tĩnh. Nó chỉ có nghĩa là hành vi hiện tại (hoặc rõ ràng) không thể dựa vào.
nschum

6
Bạn có thể muốn thêm private init() {}làm trình khởi tạo SingletonClass. để ngăn chặn ngay lập tức từ bên ngoài.
rintaro

46

Swift 1.2 trở lên hiện hỗ trợ các biến / hằng tĩnh trong các lớp. Vì vậy, bạn chỉ có thể sử dụng một hằng số tĩnh:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

35

Có một cách tốt hơn để làm điều đó. Bạn có thể khai báo một biến toàn cục trong lớp của bạn trên khai báo lớp như thế này:

var tpScopeManagerSharedInstance = TPScopeManager()

Điều này chỉ gọi init mặc định của bạn hoặc bất kỳ biến init và biến toàn cục nào dispatch_oncetheo mặc định trong Swift. Sau đó, trong bất kỳ lớp học nào bạn muốn có được một tài liệu tham khảo, bạn chỉ cần làm điều này:

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

Vì vậy, về cơ bản, bạn có thể thoát khỏi toàn bộ khối mã cá thể được chia sẻ.


3
Tại sao một "var" và rất nhiều "cho phép"?
Stephan

1
có lẽ có thể là một cho phép, tôi chỉ thử nghiệm nó với một var.
Kris Gellci

Tôi thích câu trả lời này, tuy nhiên tôi cần truy cập (Singleton) từ Trình tạo giao diện. Bất kỳ ý tưởng nào về cách tôi có thể truy cập vào tpScopeManagerSharedInstance này từ bên trong IB?. Cảm ơn.-
Luis Palacios

Đây là cách ưa thích của tôi để có một singleton. Nó có tất cả các tính năng thông thường (an toàn luồng & khởi tạo lười biếng) nó hỗ trợ cú pháp rất nhẹ: không cần phải viết TPScopeManager.sharedInstance.doIt()mọi lúc, chỉ cần đặt tên lớp của bạn TPScopeManagerClass, có khai báo này bên cạnh lớp public let TPScopeManager = TPScopeManagerClass()và khi sử dụng chỉ cần viết TPScopeManager.doIt(). Rất sạch sẽ!
Alex

Không có gì ở đây để ngăn chặn việc tạo ra các trường hợp bổ sung TPScopeManager, và do đó, nó không phải là một đơn lẻ theo định nghĩa.
Caleb

28

Swift singletons được đưa ra trong các khung công tác ca cao như các hàm lớp, ví dụ NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(). Vì vậy, nó có ý nghĩa hơn như là một hàm lớp để phản ánh hành vi này, chứ không phải là một biến lớp như một số giải pháp khác. ví dụ:

class MyClass {

    private static let _sharedInstance = MyClass()

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

Lấy singleton qua MyClass.sharedInstance().


1
nâng cấp cho nhận xét của LearnCocos2D :), cũng cho phong cách.
x4h1d

2
biến toàn cục nên được thay đổi thành biến lớp thông qua một tĩnh bên trong lớp.
malhal

2
@malhal khi một biến được đánh dấu là riêng tư nhưng bên ngoài một lớp, nó không phải là toàn cục - mà chỉ nằm trong phạm vi của tệp. Một tĩnh bên trong lớp sẽ hoạt động khá giống nhau, nhưng tôi đã cập nhật câu trả lời để sử dụng tĩnh như bạn đã đề xuất, vì nó tốt hơn nên nhóm biến thành lớp nếu bạn tình cờ sử dụng nhiều lớp trong tệp.
Ryan

1
"Swift Singletons được đưa ra trong các khung ca cao như các chức năng của lớp" ... Không phải trong Swift 3. Bây giờ chúng thường là staticcác thuộc tính.
Rob

17

Theo tài liệu của Apple , nhiều lần đã lặp đi lặp lại rằng cách dễ nhất để làm điều này trong Swift là với thuộc tính loại tĩnh:

class Singleton {
    static let sharedInstance = Singleton()
}

Tuy nhiên, nếu bạn đang tìm cách để thực hiện thiết lập bổ sung ngoài lệnh gọi hàm tạo đơn giản, thì bí mật là sử dụng một bao đóng được gọi ngay lập tức:

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

Điều này được đảm bảo là an toàn chủ đề và khởi tạo lười biếng chỉ một lần.


Làm thế nào bạn có thể thiết lập tĩnh cho phép trở lại nil?
gpichler

1
@ user1463853 - Bạn không thể, và nói chung là không nên.
Rob

16

Swift 4+

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

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

2
Điều này cần lớp cuối cùng, bạn có thể giải thích thêm về sự khác biệt không, vì tôi có vấn đề với giải pháp khác của singleton với struct
Raheel Sadiq

nên ghi đè riêng tư init () {}
NSRover

8

Nhìn vào mã mẫu của Apple, tôi đã bắt gặp mẫu này. Tôi không chắc chắn cách Swift xử lý các số liệu thống kê, nhưng đây sẽ là chủ đề an toàn trong C #. Tôi bao gồm cả thuộc tính và phương thức cho interop Objective-C.

struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}

Tôi khá chắc chắn rằng chỉ cần sử dụng cú pháp tĩnh mặc định này sẽ thực hiện tất cả các công việc gây phiền nhiễu.
Eonil

Thật không may, statics chỉ hoạt động bên trong các cấu trúc, vì vậy đó là lý do tại sao mô hình này.
user2485100

Ý định của tôi là chúng tôi không phải sử dụng dispatch_oncechất liệu. Tôi đang đặt cược vào phong cách của bạn. :)
Eonil

Không phải classtrong một khai báo lớp tương đương với statictrong một khai báo struct?
Russell Borogove

@Sam Đúng vậy. Xem mục blog của Apple về Tệp và Khởi tạo , điều này cho thấy rõ rằng cả toàn cầu và thành viên tĩnh của cấu trúc và enum đều được hưởng lợi từ dispatch_oncekhả năng này .
Cướp

5

Tóm lại,

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

Bạn có thể muốn đọc Tệp và Khởi tạo

Bộ khởi tạo lười biếng cho một biến toàn cục (cũng cho các thành viên tĩnh của structs và enums) được chạy lần đầu tiên khi toàn cầu được truy cập và được khởi chạy dispatch_onceđể đảm bảo rằng khởi tạo là nguyên tử.


4

Nếu bạn đang dự định sử dụng lớp đơn Swift của mình trong Objective-C, thiết lập này sẽ có trình biên dịch tạo (các) tiêu đề giống như Objective-C:

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

Sau đó, trong lớp Objective-C, bạn có thể gọi singleton của mình theo cách bạn đã làm trong những ngày trước Swift:

[ImageStore sharedStore];

Đây chỉ là thực hiện đơn giản của tôi.


Điều này thực sự ngắn gọn & chính xác hơn so với ví dụ khác bởi vì nó được triển khai giống như các singletons Swift khác. tức là: như các hàm lớp như thế NSFileManager.defaultManager(), nhưng vẫn sử dụng các cơ chế thành viên tĩnh an toàn luồng của Swift.
Leslie Godwin

Ca cao nói chung thực hiện những điều này như các thuộc tính tĩnh, ngày nay, không phải là các hàm lớp.
Rob

Tôi nhận thức được điều đó, nhận xét của tôi là hơn 2 tuổi. Cảm ơn đã đề cập.
Michael

4

Giải pháp đầu tiên

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Sau đó trong mã của bạn:

func someFunction() {        
    var socketManager = SocketManager        
}

Giải pháp thứ hai

func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Và sau này trong mã của bạn, bạn sẽ có thể giữ niềng răng để bớt nhầm lẫn:

func someFunction() {        
    var socketManager = SocketManager()        
}

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

Sau đó gọi nó;

let shared = MySingleton.shared

Làm tốt cho không chỉ đánh dấu initnhư private, mà còn để làm sharedMyModelnhư final! Vì lợi ích của độc giả trong tương lai, trong Swift 3, chúng tôi có thể có xu hướng đổi tên sharedMyModelthành đơn giảnshared .
Cướp

Đây là câu trả lời đúng duy nhất, ngoại trừ việc ghi đè và gọi tới super.init là sai và thậm chí sẽ không biên dịch.
Michael Morris

4

Sử dụng:

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

Cách sử dụng:

UtilSingleton.shareInstance.iVal++
println("singleton new iVal = \(UtilSingleton.shareInstance.iVal)")

Điều này giống hệt như một trong những câu trả lời tôi đã trải qua trên đường đến câu trả lời hiện tại. Vì các biến toàn cục được khởi tạo cả lười biếng và an toàn luồng, không có lý do nào cho sự phức tạp bổ sung.
David Berry

@David Khác với việc không có biến toàn cục. :)
hpique

@hpique không, chính xác như một trong những nỗ lực trước đây của tôi. Nhìn vào lịch sử chỉnh sửa.
David Berry

4

Cách tiếp cận tốt nhất trong Swift trên 1,2 là một dòng đơn, như -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

Để biết thêm chi tiết về phương pháp này, bạn có thể truy cập liên kết này .


Tại sao một NSObjectlớp con ?. Ngoài ra, điều này về cơ bản giống như stackoverflow.com/a/28436202/1187415 .
Martin R

3

Từ Apple Docs (Swift 3.0.1),

Bạn chỉ có thể sử dụng một thuộc tính loại tĩnh, được đảm bảo chỉ được khởi tạo một cách lười biếng một lần, ngay cả khi được truy cập đồng thời trên nhiều luồng:

class Singleton {
    static let sharedInstance = Singleton()
}

Nếu bạn cần thực hiện thiết lập bổ sung ngoài khởi tạo, bạn có thể gán kết quả của lệnh gọi đóng cho hằng số toàn cục:

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

3

Tôi sẽ đề xuất một enum, như bạn sẽ sử dụng trong Java, vd

enum SharedTPScopeManager: TPScopeManager {
    case Singleton
}

IMO, đây là cách Swift chính xác duy nhất để triển khai Singleton. câu trả lời khác là cách ObjC / C / C ++
Bryan Chen

Bạn có thể giải thích về câu trả lời này? Tôi không rõ Singleton được khởi tạo từ đoạn trích này
Kenny Winker

@KennyWinker Tôi không có thông tin đăng nhập của nhà phát triển Apple, do đó không nhanh chóng và vì vậy tôi không thể trả lời khi khởi tạo xảy ra. Trong Java, nó được sử dụng lần đầu tiên. Có lẽ bạn có thể thử nó với một bản in khi khởi tạo và xem nếu bản in xảy ra khi khởi chạy hoặc sau khi truy cập. Nó sẽ phụ thuộc vào cách enum được trình biên dịch thực hiện.
Howard Lovatt

@KennyWinkler: Apple vừa làm rõ cách thức hoạt động của nó, xem developer.apple.com/swift/blog/?id=7 . Trong đó, họ nói "chạy trình khởi tạo cho toàn cầu lần đầu tiên được tham chiếu, tương tự như Java" và đặc biệt. Họ cũng nói rằng dưới vỏ bọc họ đang sử dụng "Clark_once để đảm bảo rằng việc khởi tạo là nguyên tử". Do đó, enum gần như chắc chắn là con đường để đi trừ khi bạn có một số init ưa thích để làm, sau đó một tĩnh riêng là giải pháp.
Howard Lovatt

2

Chỉ để tham khảo, đây là một ví dụ về triển khai Singleton của triển khai Nested Struct của Jack Wu / hpique. Việc thực hiện cũng cho thấy cách lưu trữ có thể hoạt động, cũng như một số chức năng đi kèm. Tôi không thể tìm thấy đầy đủ ví dụ này, vì vậy hy vọng điều này sẽ giúp được ai đó!

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

Và nếu bạn không nhận ra một số chức năng đó, thì đây là một tệp tiện ích Swift nhỏ mà tôi đang sử dụng:

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

Trong swift, bạn có thể tạo một lớp singleton theo cách sau:

class AppSingleton: NSObject {

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

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

1

Tôi thích thực hiện này:

class APIClient {

}

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

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

1

Cách triển khai của tôi trong Swift ...

Cấu hìnhQuản lý.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")   

}

Truy cập globalDic từ bất kỳ màn hình nào của ứng dụng bằng cách bên dưới.

Đọc:

 println(ConfigurationManager.sharedInstance.globalDic)  

Viết:

 ConfigurationManager.sharedInstance.globalDic = tmpDic // tmpDict is any value that to be shared among the application

1

Cách tiếp cận đúng duy nhất là dưới đây.

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

    private init() {}
}

Truy cập vào

let signleton = Singleton.sharedInstance

Lý do:

  • static loại thuộc tính được đảm bảo chỉ được khởi tạo một cách lười biếng một lần, ngay cả khi được truy cập đồng thời trên nhiều luồng, do đó không cần sử dụng dispatch_once
  • Tư nhân hóa initphương thức để các thể khác không thể được tạo bởi các lớp khác.
  • final lớp như bạn không muốn các lớp khác kế thừa lớp Singleton.

Tại sao bạn sử dụng khởi tạo đóng trong khi bạn có thể sử dụng trực tiếpstatic let sharedInstance = Singleton()
abhimuralidharan

1
nếu bạn không muốn thực hiện bất kỳ thiết lập bổ sung nào thì những gì bạn nói là đúng.
applefreak

1

Sau khi thấy triển khai của David, có vẻ như không cần phải có chức năng lớp đơn instanceMethodletnó thực hiện khá giống với sharedInstancephương thức lớp. Tất cả bạn cần làm là tuyên bố nó là một hằng số toàn cầu và đó sẽ là nó.

let gScopeManagerSharedInstance = ScopeManager()

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

2
Như tôi nói trong các bình luận của tôi, lý do duy nhất để làm điều đó là vào một lúc nào đó trong tương lai bạn có thể di chuyển / ẩn biến toàn cục và có được hành vi giống như đơn lẻ hơn. Tại thời điểm đó, nếu mọi thứ đang sử dụng một mẫu nhất quán, bạn chỉ có thể tự thay đổi các lớp đơn mà không phải thay đổi cách sử dụng.
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!
}

Như đã được thảo luận ở đây, không cần thiết phải nhanh chóng kết thúc việc khởi tạo dispatch_oncevì việc khởi tạo biến tĩnh là lười biếng và tự động được bảo vệ thông qua dispatch_once Apple thực sự khuyên bạn nên sử dụng statics thay vì Clark_once vì lý do đó.
David Berry

0

Swift để nhận ra singleton trong quá khứ, không có gì khác hơn là ba cách: biến toàn cục, biến nội bộ và cách Clark_once.

Dưới đây là hai singleton tốt (lưu ý: bất kể loại văn bản nào cũng phải chú ý đến phương thức riêng tư của init (). Bởi vì trong Swift, tất cả các hàm tạo của đối tượng là công khai, cần phải viết lại init có thể được chuyển thành riêng tư , ngăn các đối tượng khác của lớp này '()' theo phương thức khởi tạo mặc định để tạo đối tượng.)

Cách 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()

Cách 2:

class AppManager {
    static let sharedInstance = AppManager()

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

// How to use?
AppManager.sharedInstance

-1

Đây là một trong những đơn giản nhất với khả năng an toàn chủ đề. Không có luồng nào khác có thể truy cập cùng một đối tượng singleton ngay cả khi họ muốn. 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!
        }
    }
}

2
Lợi thế so với thuộc tính loại tĩnh (được đảm bảo chỉ được khởi tạo một cách lười biếng một lần, ngay cả khi được truy cập đồng thời trên nhiều luồng)?
Martin R

-1

Tôi yêu cầu singleton của tôi cho phép thừa kế, và không có giải pháp nào trong số này thực sự cho phép điều đó. Vì vậy, tôi đã đưa ra điều này:

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
    }
}
  • Cách này khi làm Singleton.sharedInstance()đầu tiên nó sẽ trả về thể hiện củaSingleton
  • Khi làm SubSingleton.sharedInstance()đầu tiên, nó sẽ trả về thể hiện SubSingletonđã tạo.
  • Nếu ở trên được thực hiện, sau đó SubSingleton.sharedInstance()Singletonlà đúng và cùng một ví dụ được sử dụng.

Vấn đề với cách tiếp cận bẩn đầu tiên này là tôi không thể đảm bảo rằng các lớp con sẽ thực hiện dispatch_once_tvà đảm bảo rằng sharedInstanceVarnó chỉ được sửa đổi một lần cho mỗi lớp.

Tôi sẽ cố gắng tinh chỉnh điều này hơn nữa, nhưng sẽ rất thú vị để xem liệu có ai có cảm xúc mạnh mẽ với điều này không (ngoài thực tế là nó dài dòng và yêu cầu phải cập nhật thủ công).



-2

Tôi sử dụng cú pháp sau:

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

Điều này hoạt động từ Swift 1.2 lên đến 4 và có một số lợi thế:

  1. Nhắc nhở người dùng không thực hiện lớp con
  2. Ngăn chặn việc tạo các trường hợp bổ sung
  3. Đảm bảo sự sáng tạo lười biếng và khởi tạo độc đáo
  4. Rút ngắn cú pháp (tránh ()) bằng cách cho phép truy cập thể hiện như Singleton.instance
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.