Open longlivedrgn opened 2 months ago
(lazy) static let
(작성 ing)
class SingletonSample {
static let shared = SingletonSample()
private init() {}
// ...
}
let singletonSample = SingletonSample.shared
static let
으로 선언한다면, Dispatch_once와 Thread-safe함을 모두 만족한다.
장점으로는 역할에 매칭되는 인스턴스가 메모리에 단 하나만 존재하게 하여 그만큼 메모리를 절약할 수 있는 장점이 있습니다. 또 한번 생성을 해놓으면 계속해서 그 인스턴스를 사용하게 되기 때문에, 복잡한 절차를 통해 생성이 되는 값의 경우, 그 절차가 반복되지 않을 수 있습니다. 그만큼 접근성이 높아져 필요한 기능을 쉽게 끌어다 쓸 수 있게 됩니다.
단점으로는 해당 인스턴스가 동시에 사용이 되는 경우 데이터레이스에 쉽게 노출된다는 것입니다. 또 접근성이 높아지는 만큼 이러한 상황에 더 쉽게 노출됩니다. 그래서 되도록이면 정말 싱글톤이 될 역할인지를 고민하고 사용하는 게 좋습니다.
일반적으로 아래와 같습니다
final class King {
static let instance: Self = .init()
private init() { ... }
}
이니셜라이저를 외부에서 접근할 수 없도록 하고, 오로지 타입 프로퍼티로 접근하도록 만듭니다. shared, default등 맥락에 따라 다르게 명명되곤 합니다.
OOP의 관점에서 잘 정의하는 게 중요할 것 같은데요. 해당 타입이 정말 싱글톤이 맞는지가 고민되어야할 것 같고, mutating 함수 여부가 또 중요할 것 같습니다. 저라면 없다면 struct로 하고 아니면 class로 할 것 같네요. 쓰고 보니 mutating이 없다면 struct가 아니라 enum으로 하는 것도 또 괜찮겠다는 생각이 갑자기 드네요.
mutating이 발생하지 않는다면 크게 상관이 없는데, 만약 발생한다면, 트랜잭션을 빗대어 표현하면 원자성과 고립성을 지키는 게 굉장히 어려워집니다. 레이스 컨디션이 발생할 수 있다는 건데요. 이런 일을 하지 않는 타입을 만들 필요가 있는데, 하다 보면 점점 코드가 비대해지기 때문에 싱글톤 바깥에서 일을 처리하려고 하면 또 굉장히 까다로운 일이 됩니다. 결국 내부적으로 각 절차들의 동기화가 필요한데, 세마포어를 집어넣거나 async-await을 사용하거나 하는 방법이 필요할 것 같네요.