iOS Memory Debug Graph 분석해 프로젝트 구조 개선
넥스터즈 IT 동아리에서 두달 동안 서비스 개발.
팀 빌딩 과정까지도 포함이라 앱 규모가 작았고, 그에 반해 메모리를 매우 과하게 사용하고 있다고 느낌.
개발환경
SwiftUI를 주력으로 사용하여 개발.
분석
메모리릭이 발생한다고 생각해 이를 체크
프로파일링을 통해 Leak을 체크

Leaks에 체크되는 부분은 없었지만 메모리가 지속적으로 증가하고 있음.
다음에 생각하는 부분은 앱 구조에서 객체들을 엄청나게 생성하는게 아닐까 싶어서 확인.
(누수는 아니여도 뭔가 더티페이지 같은게 생기지 않을까란 접근)
개발 기간도 짧고, 코드의 구조나 의존성에 대한 부분 보다는 서비스 자체에 더 큰 목적이 있었음.
특히 각 Repository 및 UseCase에서 각 객체를 독립적으로 생성하는 부분 검토
초기 개발 단계에서의 코드
초기 개발 단계에서 하나의 Repository 및 UseCase를 구현.
// MARK: - 초기 개발 단계
final class RepositoryImpl: Repository {
private let apiClient = APIClinet()
private let cache = UserStorage()
init() { }
func fetch() { ... }
}
struct UseCase {
let repository: Repository = RepositoryImpl()
init() { }
func execute() { ... }
}
이렇게 구현하다보니 UseCase 및 Repository가 존재할 때 마다 지속적으로 Repository 객체가 반복 생성.
cache는 데이터 경합 상황에서 크래시를 발생하는 원인이 되기도 함.
거의 매일 배포가 있었고, 평일에는 퇴근 후에만 잠깐 가능해서 점진적으로 수정하기로 함.
1차 수정 (cache)
우선 싱글톤으로 처리하게 된 이유는 프로젝트 규모가 작아 매일마다 새로운 기능이 붙을 때 영향 범위가 상대적으로 넓었고, 같이 작업하는 분의 기능 작업 브랜치와 충돌이 매우 클 수도 있다는 생각에 1차적으로 싱글톤 전환
// MARK: - 1차 수정
final class RepositoryImpl: Repository {
private let apiClient = APIClinet.shared
private let cache = UserStorage.shared
init() { }
func fetch() { ... }
}
아래 이미지는 일차적으로 싱글톤으로 개선한 작업 그래프이다.

위의 사진은 1차적으로 개선사항으로 싱글톤을 적용한 부분에서 좌측을 확인.
서로 다른 화면에 동일한 UseCase를 사용하고 있었다.
각 화면의 ViewModel에서 각 UseCase를 객체단위로 생성하고 있었기에 2차적으로 이 부분을 해결하고자 했다.
위의 그림에서보면 동일한 UseCase 객체가 2개인 것을 확인할 수 있다. 이에 따라 RepositoryImpl도 2개
결국 DIContainer를 만들어서 객체를 주입하는 형태로 진행.
SwiftUI를 베이스로 하고 있었기에 @StateObject를 Root에서 주입하는 형태로 변경하여 단일 객체로 사용하고자 함.
2차 수정 (의존성 모으기)
// MARK: - 2차 개선사항
final class DIContainer: ObservableObject {
let profileRepository: ProfileRepository
let mbtiRepository: MBTIRepository
let matchingRepository: MatchingRepository
let subwayStationRepository: SubwayStationRepository
init() {
let apiClient = APIClient()
self.profileRepository = ProfileRepositoryImpl(apiClient: apiClient)
self.mbtiRepository = MBTIRepositoryImpl()
self.matchingRepository = MatchingRepositoryImpl(apiClient: apiClient)
self.subwayStationRepository = SubwayStationRepositoryImpl(apiClient: apiClient)
}
}
@main
struct FunchApp: App {
@StateObject private var appCoordinator = AppCoordinator()
var body: some View {
AnyView(...)
.environmentObject(diContainer)
}
}
struct ProfileViewBuilder {
private var diContainer: DIContainer
init(diContainer: DIContainer) {
self.diContainer = diContainer
}
var body: some View {
let useCase = DefaultDeleteProfileUseCase(profileRepository: diContainer.profileRepository)
let viewModel = ProfileViewModel(
useCase: useCase,
inject: diContainer.inject
)
let view = ProfileView(viewModel: viewModel)
return view
}
}
아래의 그림은 2차 개선 사항을 반영한 부분.
이제 ProfileRepositoryImpl을 확인.
우선 그래프의 선 색상이
- 불투명한 흰색인 경우에는 강한 참조
- 약간 투명한 회식인 경우에는 약한 참조
아래처럼 개선했을 때 DIcontainer만 강한참조로 ProfileRepositoryImpl을 강한 참조로 들고 있고, ViewModel에서 사용하는 UseCase의 Repositorty 영역은 DIContainer에서 한번 생성된 객체를 사용.

여기까지가 1.5.0 릴리즈에 포함
향후
개선할 수 있는 부분이 많이 보이는데, 적당한 에너지를 가지고 진행하고자 함.
네비게이션이나 여러 레이어에서 사용할 수 있는 것들을 Inject하는 형태로 수정할 예정.
'project > Funch(넥스터즈)' 카테고리의 다른 글
| 모듈화 리팩토링 과정에서 고민했던 것들 (2) | 2024.09.24 |
|---|---|
| SwiftUI 화면 dismiss 상황에서 흰 화면 나타나는 문제 (1) | 2024.09.22 |
| Swift Concurrency를 적용하면서 발생한 동시성 문제 (0) | 2024.09.20 |
| 지하철 검색 기능에 캐싱 로직 도입하기 (0) | 2024.09.20 |
| [IT 동아리 Nexters] 24기 프로젝트 회고 (0) | 2024.03.03 |