Singleton là một design pattern rất phổ biến trong phát triển phần mềm. Singleton rất đơn giản, phổ biến và dễ sử dụng. Chính vì những ưu điểm đó mà nó được rất nhiều người sử dụng.
Singleton pattern là gì?
Singleton pattern là một pattern đảm bảo rằng một (class) chỉ có một thể hiện(instance) duy nhất.
Trong Swift các bạn có thể đã được sử dụng khá nhiều các singleton khi làm việc với các framework của Apple. Dưới đây là một số ví dụ cho singleton:
// Shared URL Session
let sharedURLSession = URLSession.shared
// Default File Manager
let defaultFileManager = FileManager.default
// Standard User Defaults
let standardUserDefaults = UserDefaults.standard
Ưu điểm và nhược điểm của Singleton pattern
Ưu điểm:
- Đảm bảo một class chỉ có 1 instance duy nhất được khởi tạo.
- Ẩn các các hàm khởi tạo của class. (Đảm bảo không thể khởi tạo từ bên ngoài)
- Có thể truy cập ở bất cứ nơi nào trên project
Nhược điểm
- Khó kiểm soát và quản lý
- Hạn chế số lượng instance của class
- Khi chỉ cho phép một trường hợp cụ thể của một class
Nên sử dụng Singleton pattern khi nào?
Singleton pattern là một pattern rất hữu ích. Đôi khi bạn muốn chắc chắn rằng chỉ có một instance của một class được khởi tạo và ứng dụng của bạn chỉ sử dụng instance đó.
Ví dụ: Ứng dụng của bạn có tính năng bật tắt nhạc nền khi người dùng mở ứng dụng thì nhạc nền sẽ tự động chạy và nếu người dùng muốn tắt thì phải vào setting để tắt nó. Bạn chắc chắn phải sử dụng duy nhất một instance trong trường hợp này, bởi vì bạn không thể tạo một instance để mở và một instance để tắt vì nó là 2 instance độc lập nên không thực hiện việc cho nhau được. Vì vậy chúng ta sẽ cần đến Singleton pattern trong trường hợp này để giải quyết bài toán gọn gàng và nhẹ nhàng.
Tuy rằng Singleton pattern khá dễ sử dụng và hữu dụng nhưng bạn cần cân nhắc khi sử dụng nó tránh lạm dụng dẫn đến việc khó kiểm soát code cũng như debug.
Các bạn nên sử dụng Singleton khi mà bài toán của các bạn yêu cầu phải có một đối tượng để quản lí các nguồn tài nguyên hoặc theo dõi trạng thái của hệ thống mà nó ảnh hưởng đến nhiều nơi trong dự án.
Sử dụng class thông thường
class LocationManager{
//MARK: - Location Permission
func requestForLocation(){
//Code Process
print("Location granted")
}
}
// Truy cập vào class
let location = LocationManager() // khởi tạo class
location.requestForLocation() // gọi func
Đây là trường hợp thông thường của 1 class không sử dụng Singleton pattern để truy cập vào các func trong class. Mỗi lần muốn sử dụng một func trong class chúng ta phải khởi tạo một instance cho nó sau đó mới có thể gọi func đó được. Để giải quyết vấn đề này chúng ta tạo ra một Singleton class với static instance.
Tạo Singleton class
class LocationManager {
static let shared = LocationManager()
init(){}
func requestForLocation() {
//Code Process
print("Location granted")
}
}
// Truy cập func trong class sử dụng Singleton Pattern
LocationManager.shared.requestForLocation()
// Bạn vẫn có thể truy cập vào class một cách thông thường
let location = LocationManager()
location.requestForLocation()
Cách tốt hơn để tạo Singleton class
class LocationManager {
static let shared = LocationManager()
var locationGranted: Bool?
// thay đổi hàm init thành private để chỉ có thể khởi tạo trong class này.
private init(){}
func requestForLocation() {
//Code Process
locationGranted = true
print("Location granted")
}
}
// Gọi hàm trong class bằng việc sử dụng đoạn code dưới
LocationManager.shared.requestForLocation()
Lúc này chúng ta đã thay đổi access level của hàm khởi tạo từ internal sang private. Vì vậy nếu chúng ta cố tính sử dụng LocationManager() XCode sẽ báo lỗi: ‘LocationManager’ initializer is inaccessible due to ‘private’ protection level
Sử dụng Singleton class
Bây giờ, bất kể chỗ nào trong project bạn muốn sử dụng hàm requestForLocation của LocationManager bạn cũng chì cần sử dụng như sau:
LocationManager.shared.requestForLocation()
Sử dụng biến toàn cục (Global variables)
Đây là cách đơn giản nhất để ứng dụng Singleton pattern:
let sharedNetworkManager = NetworkManager(baseURL: API.baseURL)
class NetworkManager {
// MARK: - Properties
let baseURL: URL
// Initialization
init(baseURL: URL) {
self.baseURL = baseURL
}
}
Bằng cách khai báo một biến toàn cục, bấn kì object nào cũng có thể truy cập dến biến toàn cục đó. Trong Swift, biên toàn cục được khai báo một cách trì hoãn cho đến khi nó được gọi ra để sử dụng lần đầu tiên. Việc khởi tạo trong swift cũng dựa trên hàm dispatch_one, điều này sẽ có ích khi bạn chỉ muốn khởi tạo object của mình một lần duy nhất.
Tuy nhiên khi sử dụng cách này bạn sẽ gặp vấn đề là code của bạn sẽ khó hiểu và khó để debug hơn. Và hàm khởi tạo của class NetworkManager thì không thể khai báo là private.
Lưu ý
Mình không khuyến khích các bạn sử dụng cách tạo global variable vì các nhược điểm của nó.
Tổng kết
Vậy là giờ các bạn đã hiểu cách tạo Singleton class trong dự án của bạn. Nó khá đơn giản, dễ sử dụng và tốn ít thời gian để tạo. Nó có một số ưu điểm và nhược điểm. Nếu bạn sử dụng nhiều Singleton pattern trong dự án của mình thì sẽ khó có thể quản lý vòng đời của Singleton class của bạn. Ngoài ra, nó duy trì một trạng thái chia sẻ toàn cầu có thể thay đổi. Tuy rằng nó có các ưu điểm rõ rệt, nhưng hãy tránh lạm dụng Singleton pattern thay vào đó tốt hơn chúng ta nên sử dụng Dependency injection.