Design Pattern
[Swift] 디자인 패턴 | Singleton
iOSDEv
2024. 2. 7. 19:00
Singleton pattern
⭐️ 생성자가 여러번 호출되더라도 실제로 생성되는 객체는 하나고 최초 생성 이후에 호출된 생성자는 최초 생성자가 생성한 객체를 리턴⭐️
실제로 생성되는 객체는 하나이며 그 인스턴스는 전역적인 접근을 제공
전역변수를 사용하지 않고 객체를 하나만 생성하여 모든 코드에서 그 객체에 접근할 수 있도록 하는 목적
주로 리소스나 설정을 담당하는 클래스 활용
구조체로는 싱글톤을 만들지 못한다 이유는?
값타입이기 때문에 인스턴스화하는 순간 메모리주소가 하나 더 생기기 때문이다
기본 싱글톤 기본 예시
class MySingleton {
// 정적 상수로서 sharedInstance를 사용해 싱글톤 인스턴스에 접근
static let sharedInstance = MySingleton()
// private으로 선언하여 외부에서 인스턴스를 직접 생성할 수 없도록 함
private init() {
// 초기화 코드
}
func someFunction() {
// 싱글톤의 기능을 수행하는 메서드
}
}
// 사용 예시
let myInstance = MySingleton.sharedInstance
myInstance.someFunction()
- 유일한 객체가 될 프로퍼티에 static인 타입 메서드를 정의하고 자기 자신을 할당한다
- init()메서드 즉 초기화 메서드에 접근 제한을 설정해서 외부에서는 초기화가 불가능하도록 설정함
클래스 다이어그램
//클래스 다이어그램
+---------------------+
| MySingleton |
+---------------------+
| - sharedInstance |
| |
| + someFunction() |
| |
| - private init() |
+---------------------+
사용하는 곳
- 싱글톤은 앱 전체에 걸쳐서 유일한 객체를 만들어 사용하기 때문에 전역적으로 접근할 수 있는 유일한 객체이다 그래서 예를들어 공유되어야하는 데이터
- UserDefaults
- NotificationCenter
- URLSession
다른 예시들
enum Milk {
case chocolate
case strawberry
case banana
}
// Singleton
class MilkStorage {
static let shared = MilkStorage()
private var chocolateMilkCount = 100
private var strawberryMilkCount = 100
private var bananaMilkCount = 100
private init() { }
func release(_ milk: Milk, count: Int) {
switch milk {
case .chocolate:
chocolateMilkCount -= count
case .strawberry:
strawberryMilkCount -= count
case .banana:
bananaMilkCount -= count
}
}
func checkMilkStock() {
print("남은 재고: 초코 우유(\(chocolateMilkCount)), 딸기 우유(\(strawberryMilkCount)), 바나나 우유(\(bananaMilkCount))")
}
}
class OnlineStore {
func orderMilk(_ milk: Milk, count: Int) {
MilkStorage.shared.release(milk, count: count)
}
let naverSmartStore = OnlineStore()
let coupang = OnlineStore()
let weMakePrice = OnlineStore()
naverSmartStore.orderMilk(.chocolate, count: 15)
MilkStorage.shared.checkMilkStock()
coupang.orderMilk(.banana, count: 30)
coupang.orderMilk(.chocolate, count: 10)
MilkStorage.shared.checkMilkStock()
weMakePrice.orderMilk(.chocolate, count: 50)
weMakePrice.orderMilk(.strawberry, count: 70)
MilkStorage.shared.checkMilkStock()
//참고
//https://yagom.net/courses/design-pattern-in-swift/lessons/%ec%83%9d%ec%84%b1-%ed%8c%a8%ed%84%b4/topic/singleton/
/// 싱글톤 클래스는 `shared` 필드를 정의하여 클라이언트가 유일한 싱글톤 인스턴스에
/// 접근할 수 있게 합니다.
class Singleton {
/// 싱글톤 인스턴스에 대한 액세스를 제어하는 정적 필드입니다.
///
/// 이 구현은 싱글톤 클래스를 확장하면서 각 하위 클래스의 인스턴스를 하나만 유지합니다.
static var shared: Singleton = {
let instance = Singleton()
// ... 인스턴스 구성
// ...
return instance
}()
/// 싱글톤의 이니셜라이저는 항상 직접적인 생성 호출을 방지하기 위해 private로
/// 선언되어야 합니다.
private init() {}
/// 마지막으로, 어떤 싱글톤은 인스턴스에서 실행될 수 있는 비즈니스 로직을 정의해야 합니다.
func someBusinessLogic() -> String {
// ...
return "'someBusinessLogic' 호출의 결과"
}
}
/// 싱글톤은 복제될 수 없어야 합니다.
extension Singleton: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
return self
}
}
/// 클라이언트 코드.
class Client {
// ...
static func someClientCode() {
let instance1 = Singleton.shared
let instance2 = Singleton.shared
if (instance1 === instance2) {
print("싱글톤 작동 중, 두 변수는 동일한 인스턴스를 포함합니다.")
} else {
print("싱글톤 실패, 변수에는 서로 다른 인스턴스가 포함되어 있습니다.")
}
}
// ...
}
func testSingletonConceptual() {
Client.someClientCode()
}
//result 싱글톤 작동 중, 두 변수는 동일한 인스턴스를 포함합니다.
//참고
//https://refactoring.guru/ko/design-patterns/singleton/swift/example
장점과 단점
- 장점
- 다양한 객체들에게 공유되는 객체를 만들 수 있다는 장점
- 재사용성이 가능하여 낭비되는 메모리를 방지할 수 있다
- 단점
- 객체지향적으로 본다면 인스턴스 간에 결합도가 높아져 개방폐쇄원칙(Open Closed Priciple - OCP)를 위배함