일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 구조적 정체성
- panorama view
- 360도 뷰어
- data driven construct
- 리액트 네이티브
- 스켈레톤 통합
- react
- ios
- React Native
- ssot
- 파노라마 뷰
- Android
- 3b52.1
- requirenativecomponent
- 360도 이미지 뷰어
- 앱 성능 개선
- SwiftUI
- privacyinfo.plist
- react-native-fast-image
- 360도 이미지
- 네이티브
- 뷰 정체성
- launchscreen
- launch screen
- 뷰 생명주기
- 명시적 정체성
- native
- 라이브러리 없이
- 리액트
- React-Native
- Today
- Total
Neoself의 기술 블로그
Swift에서 싱글톤 패턴 활용하기 본문
기존에 출시한 TyTE 어플리케이션의 성능 최적화를 위해 인스턴스의 생성 및 해제 시점을 파악하고자 각 ViewModel 및 서비스 레이어에 print문을 배치시키고 홈화면에 진입한 결과, 아래 로그 내용을 볼 수 있었습니다.
MainTabView initialized
TodoService initialized
SharedTodoViewModel initialized
TodoService initialized
HomeViewModel initialized
TodoService initialized
HomeView initialized
...
로그인 및 회원가입을 완료하게 될 경우, 가장 먼저 진입하는 뷰인 MainTabView,
하위 뷰들에게 주입하고자 MainTabView에서 초기화를 거친 SharedTodoViewModel과 HomeViewModel,
그리고, 가장 마지막으로 HomeViewModel을 @ObservedObject 프로퍼티래퍼로 참조하는 HomeView가 정상적으로 1회씩 초기화되고 있는 데에 반해, 서비스 레이어인 TodoService class가 불필요하게 반복 초기화되고 있다는 것을 확인할 수 있었습니다.
// TodoService class
class TodoService {
private let apiManager = APIManager.shared
init() {
print("TodoService initialized")
}
deinit {
print("TodoService deinitialized")
}
func fetchAllTodos(mode:String) -> AnyPublisher<[Todo], APIError> {
let endpoint = APIEndpoint.fetchTodos(mode)
return Future { promise in
self.apiManager.request(endpoint) { (result: Result<[Todo], APIError>) in
promise(result)
}
}.eraseToAnyPublisher()
}
...
}
// TodoService 내부 메서드에 접근하는 뷰모델
class SharedTodoViewModel: ObservableObject {
private let todoService: TodoService
init(
todoService: TodoService = TodoService(),
) {
print("SharedTodoViewModel initialized")
self.todoService = todoService
fetchTags()
}
...
}
TodoService 내부 메서드에 접근하는 뷰모델들을 확인해본 결과, 각 뷰모델이 자신만의 TodoService 인스턴스를 생성 및 사용하고 있는 것이 원인인 것을 알 수 있었습니다... 한개만 존재하여도 되는 인스턴스가 여러번 생성되는 것이기에, 이는 메모리를 불필요하게 차지해 성능 저하의 주요 원인이 됩니다.
따라서 TodoService를 싱글톤으로 구현해, 앱 전체에서 하나의 인스턴스를 공유해 사용하게끔 리팩토링을 진행하였습니다!
여기서 싱글톤 패턴이란 특정 인스턴스가 전체 어플리케이션에서 단일로 존재하도록 보장하는 디자인 패턴입니다. 이 패턴을 사용하게 될 경우, 위 사례와 같이 동일한 기능을 하는 여러 인스턴스가 불필요하게 메모리를 차지하는 문제를 해결할 수 있습니다.
class TodoService {
static let shared = TodoService() // 전역적으로 접근 가능한 단일 인스턴스 생성
private let apiManager = APIManager.shared
init() {
print("TodoService initialized")
}
deinit {
print("TodoService deinitialized")
}
func fetchAllTodos(mode:String) -> AnyPublisher<[Todo], APIError> {
let endpoint = APIEndpoint.fetchTodos(mode)
return Future { promise in
self.apiManager.request(endpoint) { (result: Result<[Todo], APIError>) in
promise(result)
}
}.eraseToAnyPublisher()
}
...
}
static let shared = TodoService() 구문을 추가하여 전역적으로 접근이 가능한 단일 인스턴스를 서비스 레이어에서 생성해준 후에,
class HomeViewModel: ObservableObject {
private let todoService: TodoService
init(
todoService: TodoService = TodoService.shared
) {
self.todoService = todoService
fetchTodos()
}
...
}
서비스 레이어 내부 메서드를 접근해야하는 뷰모델 내부에는 TodoService.shared 로 초기에 생성된 단일 인스턴스에 접근할 수 있습니다.
이제 위와 같이 서비스 레이어와 뷰모델를 수정한 후, 앱을 진입하게 될 경우,
MainTabView initialized
TodoService initialized
SharedTodoViewModel initialized
HomeViewModel initialized
HomeView initialized
TodoService가 하나만 생성되는 것을 확인할 수 있었습니다!
싱글톤 패턴은 위와 같이 앱 전체에서 일관된 데이터 접근이 필요하고, 인스턴스가 불필요하게 반복 생성될 경우 매우 강력하지만, 과도하게 사용할 경우, 아래와 같은 문제점에 유의해야 합니다.
1. 컴포넌트들 사이의 의존성이 증가해 재사용성이 저하될 수 있음.
2. 의존성 주입과 같은 패턴에 비해, 의존성이 숨겨져있기 때문에 코드 가독성이 떨어지고, 유지보수에 드는 리소스가 커질 수 있음.
따라서, 싱글톤 사용 시에는 의존성 주입과 같은 다른 패턴과 적합성을 비교해보는 등 프로젝트의 요구사항에 따라 신중히 고려해봐야 합니다.
패턴 채택 시 정답은 존재하지 않지만, 싱글톤 패턴이 특히 유용한 상황은 다음과 같습니다.
- DB 연결 관리자, 네트워크 연결 관리자와 같은 공유 리소스를 관리하는 class
- 앱의 전역 상태를 관리하는 class
감사합니다.
'개발지식 정리 > Swift' 카테고리의 다른 글
SwiftUI에서 애플, 구글 로그인 구현하기 (with Node.js) (1) | 2024.10.09 |
---|---|
React Native와 비교해보는 Swift,SwiftUI (5) | 2024.10.08 |
Keychain과 UserDefaults를 활용한 사용자 정보 관리 (3) | 2024.10.07 |
ARC와 약한 참조를 바탕으로 하는 메모리 관리 최적화 (1) | 2024.10.02 |
SwiftUI에서 MVVM + 서비스 레이어 패턴 구현하기 (1) | 2024.10.01 |