Singleton — один из самых обсуждаемых паттернов в iOS разработке. Давайте разберем, как правильно его реализовать в Swift с учетом всех нюансов языка.
Самая простая и распространенная форма:
final class AppManager {
static let shared = AppManager()
private init() {}
func doWork() {
print("Performing work...")
}
}
// Использование:
AppManager.shared.doWork()
Ключевые элементы:
final
— запрещает наследованиеstatic let shared
— глобальная точка доступаprivate init()
— предотвращает создание новых экземпляровSwift гарантирует потокобезопасность инициализации статических свойств, но для работы с изменяемыми данными нужны дополнительные меры:
final class DataCache {
static let shared = DataCache()
private init() {}
private var cache = [String: Data]()
private let queue = DispatchQueue(label: "com.app.datacache", attributes: .concurrent)
func set(_ data: Data, for key: String) {
queue.async(flags: .barrier) {
self.cache[key] = data
}
}
func get(for key: String) -> Data? {
queue.sync {
return cache[key]
}
}
}
Swift по умолчанию инициализирует статические свойства лениво, но можно добавить кастомную логику:
final class ComplexService {
static let shared: ComplexService = {
let instance = ComplexService()
instance.configure()
return instance
}()
private init() {}
private func configure() {
// Дополнительная настройка
}
}
Чтобы избежать проблем с тестированием, можно использовать протокол:
protocol AnalyticsProtocol {
func track(event: String)
}
final class AnalyticsService: AnalyticsProtocol {
static let shared: AnalyticsProtocol = AnalyticsService()
private init() {}
func track(event: String) {
// Реальная реализация
}
}
// Мок для тестов
class AnalyticsServiceMock: AnalyticsProtocol {
func track(event: String) {
// Тестовая реализация
}
}
Для простых случаев можно использовать enum:
enum AppSettings {
static var apiKey: String = "default_key"
static var isDebugMode: Bool = true
static func reset() {
apiKey = "default_key"
isDebugMode = true
}
}
// Использование:
AppSettings.apiKey = "new_key"
var globalManager = AppManager() // Плохо!
class UnsafeSingleton {
static var shared: UnsafeSingleton!
init() {
UnsafeSingleton.shared = self // Опасная инициализация
}
}
class BadSingleton {
static var shared = BadSingleton()
init() {} // Не private!
}
// Можно сделать так:
BadSingleton.shared = BadSingleton() // Ужасно!
class NetworkService {
static let production = NetworkService(baseURL: "https://api.example.com")
static let staging = NetworkService(baseURL: "https://staging.api.example.com")
let baseURL: String
private init(baseURL: String) {
self.baseURL = baseURL
}
}
class AppEnvironment: ObservableObject {
@Published var userSession: UserSession?
}
// В иерархии View:
ContentView()
.environmentObject(AppEnvironment())
Правильная реализация Singleton в Swift требует:
final
классаstatic let shared
для доступаprivate init()
Лучшие практики:
Помните: Singleton — это мощный инструмент, но его часто используют там, где он не нужен. Прежде чем реализовывать Singleton, спросите себя: "Действительно ли мне нужен глобально доступный единственный экземпляр?"