Contents
Chào bạn tới với Fx Studio, bài viết này sẽ nói về một chủ đề rất quen thuộc trong giới lập trình Swift hay giới lập trình nói chung. Đó là Singleton Pattern. Một mẫu design pattern khá là phổ biến trong hầu hết các ngôn ngữ lập trình.
Bây giờ chúng ta sẽ tìm hiểu nó trong Swift sẽ như thế nào? Và cách viết singleton thế nào là ổn nhất? Nếu bạn chưa tìm hiểu về Swift thì có thể bổ sung kiến thức trước khi vào bài:
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Singleton
Ít nhiều bạn dev iOS thì cũng một lần trong đời thấy qua các đoạn code sau:
// Shared URL Session let sharedURLSession = URLSession.shared // Default File Manager let defaultFileManager = FileManager.default // Standard User Defaults let standardUserDefaults = UserDefaults.standard // Default Payment Queue let defaultPaymentQueue = SKPaymentQueue.default()
Đó chính là những mẫu singleton mà Apple sử dụng trong core hệ thống của họ. Vậy singleton là gì?
Singleton Pattern là pattern đảm bảo rằng một lớp chỉ có một thể hiện (instance) duy nhất và trong đó cung cấp một cổng giao tiếp chung nhất để truy cập vào lớp đó.
Singleton pattern là một pattern vô cùng hữu dụng. Đôi khi bạn muốn chắc chắc chắn rằng chỉ có một instance của một đối tượng được khởi tạo và ứng dụng của bạn chỉ sự dụng instance đó . Đó là mục tiêu chính và duy nhất của singleton pattern.
Nhưng mọi vấn đề đều có mặt tốt và mặt xấu, singleton cũng không tránh được nó.
- Tốt
- Nhanh, tiện lợi
- Truy cập mọi lúc, mọi nơi
- Lưu trữ dữ liệu đơn giản
- Truyền dữ liệu đơn giản
- Xấu
- Khó kiểm soát
- Khó quản lý
- Nguy hiểm khi dùng chung nhiều Thread
- Sự quá lệ thuộc vào nó trong điều phối dữ liệu
Xấu hay Tốt thì do bản thân mình quyết định. Nhưng để sử dụng nó một cách tối ưu nhất thì bạn tham sao các kiểu singleton dưới đây.
2. Các kiểu Singleton
2.1. Biến toàn cục
Phương pháp đơn giản để ứng dụng Singleton Pattern là tạo ra một biến toàn cục (Global Variables).
Tạo ra giữa trời đất này và ảnh hưởng tới toàn bộ thế gian.
Tham khảo code ví dụ sau:
//1 class MyClass { init() {} } //2 let shareMyClass = MyClass()
Trong đó:
- Khai báo 1 class hoặc bạn có thể sử dụng bất kì class nào bạn muốn
- Khai báo 1 biến hoặc 1 hằng và sử dụng nó
Tuỳ thuộc vào phạm vị bạn sử dụng như thế nào thì tầm ảnh hưởng của nó sẽ theo như vậy.
2.2. Biến Static
Tham khảo đoạn code sau:
class User { init() {} } static var defaultUser = User()
Chúng ta có thể thấy, thì một biến/hằng bất kì với khai báo static
thì nó sẽ không bị mất đi. Hiển nhiên, bạn có thể truy cập tới nó ở bất kì đâu. Và nó trở thành Singleton.
Có 1 điểm hạn chế cho việc khai báo biến đó giữa trời giữa đất như vậy. Đó là hạn chế về mặt quản lý.
class User { //singleton static var defaultUser = User(name: "default user") //properties var name: String //init init(name: String) { self.name = name } //method func say() { print(name) } } User.defaultUser.say()
Chúng ta di chuyển đối tượng stactic defaultUser
vào trong class User. Khi đó việc truy cập tới defaultUser
có vẻ dễ hiểu hơn nhiều.
Đây cũng là cách khuyến khích bạn nên làm.
2.3. Kiểu function
Đôi khi bạn lại thấy nhiều đối tượng singleton là shared
hay shared()
. Thì đó cũng là kiểu singleton với cách truy cập là gọi function
của Class để trả về 1 đối tượng duy nhất.
class User { //singleton private static var defaultUser = User(name: "default user") static func shared() -> User { return defaultUser } //properties var name: String //init init(name: String) { self.name = name } //method func say() { print(name) } } User.shared().say()
Với kiểu này thì bạn có thể che dấu đi biến static
trên kia. Nhìn trông pro hơn nhiều.
Nôm na thì chúng ta có 3 kiểu như trên, còn lại là viết singleton như thế nào là ổn nhất? Và cách nào, kiểu nào là tốt nhất? Thì tham khảo tiếp phần sau.
3. Cách viết Singleton
Chúng ta giả định có 1 class tên là DataManager
có nhiệm vụ quản lý Database trong ứng dụng. Cách tốt nhất để quản lý và xử lý thì cần xử lý tập trung với 1 đối tượng duy nhất. Với yêu cầu trên thì chúng ta sẽ viết singleton cho class này.
class DataManager { //properties var databaseName: String = "" // init init() {} // config database func configDatabase(databaseName: String) { self.databaseName = databaseName } //open database func open() { print("open: \(databaseName)") } //save database func save() { print("save: \(databaseName)") } }
3.1. Struct muôn nơi
//singleton class var shared: DataManager { struct Static { static var instance = DataManager() } return Static.instance }
Thêm đoạn code trên vào trong class User, để tạo ra 1 singleton cho nó. Sử dụng như sau:
DataManager.shared.open()
Về bản chất thì chúng cũng chỉ là 1 biến static của Class. Nhưng giờ đây nó được lồng vào 1 struct. Bản chất của nó gọi là type property
của class.
Với cách này thì kiểu gừng càng già càng cay.
3.2. Biến toàn cục
Đã được giải thích ở trên. Bạn chỉ cần đưa nó ra ngoài class là xong.
//1 private let sharedDataManager = DataManager() //2 class var shared: DataManager { return sharedDataManager }
Trong đó:
- Tạo 1 biến
static
ở đâu đó cũng được, tốt nhất ngoài phạm vi class - Edit lại function
shared
ở trên, với return về đối tượngstatic
kia
Với cách này thì kiểu nước sông không phạm nước giếng.
3.3. One-line
Chúng ta lại tham khảo cách viết tiếp theo
class DataManager { //singleton static let shared = DataManager() ... }
Singleton được tạo ra với 1 dòng duy nhất. Về sử dụng thì truy suất trực tiếp
DataManager.shared.open()
Với cách này thì kiểu ngon, bổ , rẻ.
3.4. Cách đề nghị
Từ trên xuống tới đây, thì thật giả lẫn lộn. Giờ chúng ta tham khảo cách mà theo mình là bạn nên sử dụng. Tham khảo đoạn code sau:
class DataManager { //singleton private static var sharedDataManager: DataManager = { let dataManager = DataManager() //config dataManager.configDatabase(databaseName: "BD") return dataManager }() class func shared() -> DataManager { return sharedDataManager } //properties var databaseName: String = "" // init private init() {} // config database func configDatabase(databaseName: String) { self.databaseName = databaseName } //open database func open() { print("open: \(databaseName)") } //save database func save() { print("save: \(databaseName)") } } DataManager.shared().open()
Trong đó:
sharedDataManager
là mộttype property
và được khai báoprivate
sharedDataManager
được gián giá trị bằng 1closure
với return là kiểuDataManager
- Với việc sử dụng
closure
thì đảm bảo cho việc mình có thể chạy thêm các function cần thiết cho đối tượng singleton. Thường sẽ là các hàm config dữ liệu shared()
là mộttype method
, sử dụng bằng cách dùng tên class để gọi. Method return về đối tượngsharedDataManager
- Để đảm bảo cho đối tượng singleton là duy nhất, thì
private init
. Không cho phép việc tạo thêm đối tượng khác nữa.
Các cách viết trên chỉ mang tính chất tham khảo. Tuỳ thuộc vào yêu cầu của dự án mà bạn sẽ quyết định dùng cách nào.
Chúc bạn thành công với singleton.
Tạm kết
- Biết về Singletion Pattern
- Các kiểu Singleton
- Các cách viết Singleton
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)