안녕하세요! 피피아노입니다 🎵
이번 포스팅에서는 개발할 때 자주 쓰이는 싱글톤 패턴에 대해서 정리를 해보려고 합니다.
Singleton Pattern이란?
싱글톤 패턴이란,
특정 클래스의 인스턴스가 애플리케이션 전체에서 단 하나만 존재하도록 보장하는 디자인패턴입니다.
이 패턴은 전역적으로 접근 가능한 단일 객체가 필요할 때 유용하게 사용됩니다.
앱 전체에서 공유되어야 하는 리소스나 설정이 있을 때, 여러 개의 인스턴스가 생성되면 데이터 불일치나 리소스 낭비가 발생할 수 있는데 그럴 때 싱글톤 패턴을 사용하면 이런 문제를 해결할 수 있습니다.
Swift에서 싱글톤 구현
Swift에서는 static let을 사용해서 간단하게 싱글톤을 구현할 수 있습니다.
class NetworkManager {
static let shared = NetworkManager()
private init() {
print("NetworkManager 인스턴스가 생성되었습니다.")
}
func fetchData() {
print("데이터를 가져오는 중...")
}
}
코드를 하나씩 살펴보면
static let shared = NetworkManager() 이 부분이 싱글톤의 핵심입니다.
static 키워드는 타입 프로퍼티를 만들어서 클래스 레벨에서 단 하나의 인스턴스만 존재하도록 해줍니다.
let으로 선언했기 때문에 한 번 생성한 후에는 변경할 수 없고 Swift의 타입 프로퍼티는 lazy하게 초기화되며, 처음 접근할 때 자동으로 생성됩니다.
private init() 생성자를 private으로 선언하는 것도 싱글톤에서 중요한 부분입니다.
이렇게 하면 외부에서 NetworkManager()를 직접 호출할 수 없게 되고, 클래스 내부의 shared 프로퍼티를 통해서만 인스턴스에 접근할 수 있습니다.
싱글톤 사용
// 첫 번째 접근 - 이때 인스턴스가 생성
NetworkManager.shared.fetchData()
// 두 번째 접근 - 기존 인스턴스를 재사용
NetworkManager.shared.fetchData()
// 여러 곳에서 접근해도 같은 인스턴스
let manager1 = NetworkManager.shared
let manager2 = NetworkManager.shared
print(manager1 === manager2) // true - 같은 인스턴스를 참조
위 코드에서 NetworkManager.shared를 처음 호출하는 순간, init() 메서드가 실행되고 인스턴스가 생성됩니다.
그 이후의 모든 호출은 이미 만들어진 동일한 인스턴스를 반환하게 됩니다.
=== 연산자로 확인을 해보면 true가 나오게 되는데, manager1 과 manager2가 메모리 상에서 정확히 같은 객체를 가리키고 있음을 알 수 있습니다.
하지만 만약 생성자가 private이 아니라면?
let manager1 = NetworkManager.shared
우리는 위 코드처럼 사용되길 원하지만
let manager2 = NetworkManager() // 새로운 인스턴스 생성
let manager3 = NetworkManager() // 또 다른 인스턴스 생성
이런 식으로 여러 개의 인스턴스가 만들어질 수 있습니다.
이렇게 되면 싱글톤의 의미가 사라지기 때문에 생성자가 private이어야 합니다.
싱글톤의 장단점
싱글톤의 장점을 정리해보자면 아래처럼 정리해볼 수 있습니다.
- 전역 접근성: 앱의 어디서든 쉽게 접근 가능. 여러 계층을 거쳐서 객체를 전달할 필요 없음
- 리소스 관리: DB 연결, 네트워크 세션, 파일 핸들러 등 비용이 큰 리소스를 하나만 생성하여 효율적으로 관리 가능
- 상태 공유: 앱 전체에서 일관된 상태를 유지해야 할 때 유용
싱글톤의 장점을 보면 장점이 명확한데 그렇다면 장점만 있느냐 하면 그건 아닙니다.
단점도 정리해보자면
- 테스트 어려움: 싱글톤은 전역 상태를 만들기 때문에 단위 테스트가 어려움
(각 테스트는 독립적이어야 하는데, 싱글톤의 상태가 테스트 간에 공유되면서 테스트가 서로 영향을 미칠 수 있음) - 의존성 은닉: 코드를 읽을 때 클래스가 어떤 의존성을 가지는지 명시적으로 드러나지 않음
(생성자나 메서드 파라미터로 의존성을 주입받는 것과 달리, 싱글톤은 내부에서 직접 접근하기 때문) - 결합도 증가: 싱글톤을 사용하는 모든 코드가 해당 싱글톤에 강하게 결합됨
(나중에 싱글톤을 다른 구현체로 교체하거나 제거하려면 사용하는 모든 곳을 수정해야 함)
싱글톤 사용 시 주의사항
1. 정말 싱글톤이 필요할까?
모든 유틸리티 클래스를 싱글톤으로 만들 필요는 없습니다.
단순히 여러 메서드를 그룹화하는 것이 목적이라면, 정적 메서드나 네임스페이스 enum을 사용하는 것이 더 나을 수도 있습니다.
2. 스레드 안정성
Swift의 타입 프로퍼티는 기본적으로 스레드 세이프하게 초기화됩니다. 하지만 싱글톤 내부의 프로퍼티를 여러 스레드에서 동시에 변경한다면 문제가 발생할 수 있습니다.
3. 의존성 주입 고려
테스트 가능성과 유연성을 높이려면 프로토콜과 의존성 주입을 함께 사용할 수 있습니다.
protocol NetworkManagerProtocol {
func fetchData()
}
class NetworkManager: NetworkManagerProtocol {
static let shared = NetworkManager()
private init() {}
func fetchData() {
print("실제 네트워크 요청")
}
}
class MockNetworkManager: NetworkManagerProtocol {
func fetchData() {
print("테스트용 Mock 데이터")
}
}
class ViewModel {
private let networkManager: NetworkManagerProtocol
// 기본값으로 싱글톤을 사용하지만, 테스트에서는 Mock을 주입할 수 있음
init(networkManager: NetworkManagerProtocol = NetworkManager.shared) {
self.networkManager = networkManager
}
func loadData() {
networkManager.fetchData()
}
}
이렇게 하면 실제 앱에서는 싱글톤을 쓰면서도, 테스트 시에는 Mock 객체를 주입할 수 있어서 테스트가 훨씬 쉬워집니다.
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
| [Swift] Swift 6로 마이그레이션 하기 (3) | 2026.03.04 |
|---|---|
| [Swift] Swift 동시성 사용하기 (0) | 2026.02.09 |
| [Swift] 3D 스캔 앱을 로컬 서버와 연결하기 (2) | 2025.12.30 |
| [Swift] SwiftData 모델 구조 변경 시 런타임 에러 해결하기 (0) | 2025.10.13 |
| [Swift] 앱 인텐트 알아보기 (5) | 2025.09.03 |
