| 메인 화면(음악 추천) | 검색 결과 화면 | 상세 정보 화면 |
|
|
|
| Name | Description |
|---|---|
| UIKit | iOS 앱의 UI를 구축하고 사용자 인터페이스를 관리하는 기본 프레임워크 |
| SnapKit | Auto Layout 제약 조건을 간결하게 선언하여 코드의 가독성과 유지보수성을 높이기 위해 사용 |
| RxSwift | 비동기 이벤트 흐름을 선언적으로 처리하고 다양한 Operator로 반응형 프로그래밍 구현을 위해 사용 |
iTunesAppSuhyun
┣ App
┃ ┣ AppDelegate
┃ ┣ DIContainer
┃ ┗ SceneDelegate
┣ Data
┃ ┣ ErrorType // NetworkError Type
┃ ┣ Extensions
┃ ┣ Network
┃ ┃ ┣ Base
┃ ┃ ┣ DTO
┃ ┃ ┗ ITunesNewtork
┃ ┗ Repository
┣ Domain
┃ ┣ Model
┃ ┣ RepositoryProtocol
┃ ┗ UseCase
┣ Presentation
┃ ┣ Detail
┃ ┣ Home
┃ ┣ SearchResult
┃ ┗ Util
┃ ┃ ┣ Extensions
┃ ┃ ┗ ImageLoader // 이미지 캐시
┗ Resource
┃ ┣ Assets.xcassets
┃ ┣ Base.lproj
┗ ┗ Info.plist
|
|
본 프로젝트는 Clean Architecture를 기반으로 명확한 책임 분리를 통해 모듈화를 구현했습니다.
- Presentation, Domain, Data, App 레이어로 나누어 기능별로 모듈화했으며, 각 레이어는 의존성 방향을 지키며 느슨하게 결합되어 있습니다.
- NetworkSerivce, Repository, UseCase 등이 각기 다른 책임을 가지므로 유지 보수와 테스트가 용이한 구조입니다.
실제 기능을 추가하고 테스트를 진행하면서 명확한 책임 분리와 모듈간 의존성을 최소화한 Clean Architecture의 장점을 느낄 수 있었습니다.
iTunesAppSuhyun
┣ App // 앱의 진입점, 의존성 관리
┣ Data // Repository, Network 구현
┣ Domain // Model, UseCase, RepositoryProtocol 구현
┣ Presentation // ViewController, View, ViewModel 구현
┗ Resource // Assets, info.plist 등
프로젝트 전반에 걸쳐 의존성 역전 원칙(DIP)을 따르기 위해 프로토콜 기반 추상화를 적용했습니다.
예를 들어, 고수준 모듈인 UseCase가 저수준 모듈인 Repository를 의존하게 되면 DIP에 위배되어 유지보수가 비효율적일 수 있습니다.
따라서 Repository를 프로토콜로 추상화하여 UseCase에서 Repository Protocol을 의존하게 되면 DIP 원칙을 지키면서 유지보수성이 높아질 수 있습니다.
// Domain Layer
protocol MovieRepositoryProtocol {
func fetchMovie(keyword: String, country: String, limit: Int) async throws -> [Movie]
}
// Data Layer
final class MovieRepository: MovieRepositoryProtocol {
// 실제 구현부
func fetchMovie(keyword: String, country: String, limit: Int) async throws -> [Movie] {
...
}
}모듈화를 활용한 책임 분리, 추상화, 의존성 주입으로 테스트 용이성을 직접 체감했습니다.
본 테스트에서는 UseCase, Repository 테스트 보단 Network의 테스트에 중점을 두어 진행했습니다.
Network 테스트 진행 과정에서 expectation, wait을 활용한 비동기 테스트 구현 방법을 학습할 수 있었습니다.
테스트 코드 바로가기 👉
imageURL을 Key, UIImage를 Value로 NSCache에 저장하여 동일한 URL의 이미지를 반복 다운로드 하지 않고 캐시된 이미지를 반환해 이미지 로딩 시간을 줄였습니다.
NSCache는 메모리 캐시로 앱이 실행되는 동안에만 캐시가 유지되며, 앱 종료 시 모든 캐시가 소멸됩니다.
Actor를 사용하여 이미지 중복 다운로드를 방지하고 만료 기간을 저장하여 여러 스레드에서 접근해도 캐시 데이터의 일관성을 유지했습니다.
TTL(Time-To-Live)을 설정하여 캐시 만료 시점을 관리하고, 캐시 메모리의 용량을 제한하여 메모리를 효율적으로 관리할 수 있도록 설계했습니다.
이미지 캐시 코드 바로가기 👉
현재 DetailViewController를 여러 번 호출하고 dismiss를 해도 DetailViewController와 관련된 객체들이 메모리에서 해제되지 않아 메모리 누수가 발생했습니다.
DetailViewController가 제대로 deinit되지 않아 내부 객체들도 메모리에서 해제되지 않았습니다.
DetailViewController의 하위뷰인 PreviewView에서 영상 재생을 위해 딜리게이트 패턴이 사용되었는데 이때 순환참조가 발생했었습니다.
// PrevieView.swift
var delegate: PreviewViewDelegate?순환참조를 막기 위해 약한 참조로 선언하여 순환 참조 문제를 해결했습니다
// PrevieView.swift
weak var delegate: PreviewViewDelegate?







