안녕하세요, iOS 개발하는 루피입니다.
오늘은 공식문서를 바탕으로 디자인 패턴 중 싱글톤에 관해 정리해 보겠습니다.
바로 시작하겠습니다.
Singleton(싱글톤)
싱글톤패턴은 하나의 클래스에서 오직 하나의 인스턴스만 가지는 디자인 패턴으로 클래스의 전역적으로 접근 가능한 공유 인스턴스를 제공하기 위해 사용됩니다.
앱 전반에서 공유되는 리소스나 서비스를 통합적으로 관리하기 위해 직접 싱글톤을 생성할 수 있습니다.
예를 들어, 효과음을 재생하는 오디오 채널이나 HTTP 요청을 처리하는 네트워크 매니저를 싱글톤으로 구현할 수 있습니다.
이해가 가시나요? 조금 더 자세히 설명해 보도록 하겠습니다.
인스턴스를 생성하는 데는 생각보다 많은 비용이 드는데요. 이게 단순 작업보다는 I/O 바운드 경우를 확연하게 두드러집니다. 이때, 싱글톤을 사용하면 인스턴스 생성 비용을 줄여 속도를 높일 수 있게 됩니다.
그렇기에 인스턴스 생성에 많은 비용이 드는 데이터베이스 연결 모듈에 많이 쓰이며, 인스턴스 생성을 효율적을 한다는 장점이 있습니다. 하지만, 그에 반한 단점도 명확한데요, 바로 TDD를 할 때 불편한 단점이 있다는 것입니다.
- IO 바운드 : 작업 속도가 입출력 장치의 성능에 의해 제한되는 상황. 이 경우, CPU는 데이터 처리 준비가 되어 있어도 I/O 작업(파일 읽기, 네트워크 요청 등)을 기다리며 대기 상태에 머물게 됩니다.
- TDD : 테스트 주도 개발로, 코드를 작성하기 전에 테스트를 먼저 작성하는 소프트웨어 개발 방법론. 이는 코드 품질을 높이고 유지보수를 용이하게 하며, 설계를 개선하 데 효과적입니다.
그렇다면 왜? TDD를 할 때 불편할까요?? 싱글톤 패턴은 전역적으로 하나의 인스턴스를 공유하기 때문에, 한 테스트에서 상태를 변경하면 다른 테스에 영행을 미칠 수 있기 때문입니다!!. 이는 테스트 간의 상태 격리를 어렵게 만들어 예측 불가능한 결과를 초래할 수 있습니다.
코드를 통해 싱글톤 알아보기
싱글톤이 아닌 경우
import Foundation
class Rectangle {
var height: Double
var width: Double
init(height: Double, width: Double) {
self.height = height
self.width = width
}
}
let a = Rectangle(height: 1.0, width: 2.0)
let b = Rectangle(height: 1.0, width: 2.0)
if a === b {
print("아주 똑같구만")
} else {
print("달라달라")
}
어떤 결과가 나올 거라고 생각하시나요?? 당연히 파라미터 값이 똑같은 인스턴스 2개를 생성하고 비교 했으니 당연히 같다는 결과값이 나올거 같나요?? 정답은 틀렸습니다. a와 b는 서로 다른 인스턴스입니다. 왜일까요?? 클래스는 참조타입입니다.
참조 타입은 객체를 생성할 때마다 새로운 메모리 주소를 할당받습니다. 따라서 a와 b는 동일한 클래스에서 생성되었더라도, 서로 다른 메모리 주소를 참조하기에 서로 다르다는 결과를 보이게 됩니다.
싱글톤인 경우
그렇다면 싱글톤 방식으로 코드를 작성한다면 어떻게 될까요? 같이 보시죠.
import Foundation
class Rectangle {
static let shared = Rectangle(height: 1.0, width: 2.0) // 싱글톤 인스턴스
var height: Double
var width: Double
private init(height: Double, width: Double) { // 외부에서 생성 불가
self.height = height
self.width = width
}
}
let a = Rectangle.shared
let b = Rectangle.shared
if a === b { // 참조 비교 (항상 true)
print("아주 똑같구만")
} else {
print("달라달라")
}
지금은 어떤 결과가 나올까요? 한 번 생각해보시죠. 3...2...1 정답은 "아주 똑같구만" 입니다. 클래스에서 만든 하나의 인스턴스를 공유하기 때문에 이처럼 같다는 결과 값이 나올 수 있습니다.
싱글톤이 왜 TDD에 불리할까?
위에서 말씀드린 거처럼 싱글톤 패턴은 전역적으로 하나의 인스턴스를 공유하기 때문에, 한 테스트에서 상태를 변경하면 다른 테스트에 영향을 미칠 수 있기 때문이고, 이는 테스트 간의 상태 격리를 어렵게 만들어 예측 불가능한 결과를 초래할 수 있다고 말씀드렸는데요. 한 번 코드를 통해 살펴보겠습니다.
import Foundation
class Rectangle {
@MainActor static let shared = Rectangle(height: 1.0, width: 2.0) // 싱글톤 인스턴스
var height: Double
var width: Double
func increment() {
height -= 0.1
}
private init(height: Double, width: Double) { // 외부에서 생성 불가
self.height = height
self.width = width
}
}
// 싱글톤 인스턴스 접근
let a = Rectangle.shared
let b = Rectangle.shared
a.increment()
print(b.height)
저는 분명 a의 높이 값만 변경하고 b의 높이 값은 그대로 가져가고 싶었습니다.
하지만 a의 높이 값을 조정하는 함수를 실행하니 b의 높이 값도 같이 변경된 것을 확인할 수 있습니다.
이처럼 테스트는 독립적인 인스턴스를 만들어 개별적으로 진행하는 것이 유용하지만, 같은 인스턴스를 공유할 경우 이러한 문제점이 야기될 수 있습니다.
오늘도 화이팅입니다!
'iOS > Swift' 카테고리의 다른 글
| [Swift] Delegate를 사용해 객체의 동작을 커스텀하기 (0) | 2025.01.16 |
|---|---|
| [Swift] 스위프트에서 KVO 사용하기 (0) | 2025.01.16 |
| [Swift] 함수형 프로그래밍(Functional Programming) 2/2 (0) | 2025.01.13 |
| [Swift] 함수형 프로그래밍(Functional Programming) 1/2 (1) | 2025.01.13 |
| [Swift] 클로저(Closure) 2/2 (0) | 2025.01.13 |