Skip to content

# 메인화면 구성 완료#6

Open
TyrHanz wants to merge 3 commits into
main_hanfrom
dev_han
Open

# 메인화면 구성 완료#6
TyrHanz wants to merge 3 commits into
main_hanfrom
dev_han

Conversation

@TyrHanz
Copy link
Copy Markdown
Collaborator

@TyrHanz TyrHanz commented Mar 17, 2026

🛠 작업 내용 (What I did)

  • MVVM 구조 설계
  • RxSwift 형식의 input, output 설계
  • cardCell, listCell 구현
  • 메인화면 설계 및 구현
  • API 서비스 함수 생성
  • 음악 모델 설계

💻 구현 상세 (Implementation Details)

  • MVVM + RxSwift input/output 설계로 구현
  • 메인뷰는 SearchController와 CollectionView로 설계
  • CollectionView는 DiffableDataSource를 사용하여 구현
  • 2가지 Cell (Card, list)로 뷰 설계 및 Compositiional Layout으로 구현

📸 스크린샷 (Screenshots)

스크린샷 2026-03-17 오후 2 59 43

- MVVM 구조 설계
- RxSwift 형식의 input, output 설계
- cardCell, listCell 구현
- 메인화면 설계 및 구현
- API 서비스 함수 생성
- 음악 모델 설계
@TyrHanz TyrHanz self-assigned this Mar 17, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

메인 화면 구현을 위한 MVVM 아키텍처와 RxSwift를 사용한 반응형 프로그래밍 접근 방식이 좋습니다. 전반적으로 코드 구조가 잘 잡혀있지만, 의존성 주입(Dependency Injection)을 적용하여 컴포넌트 간의 결합도를 낮추고 테스트 용이성을 높이는 방향으로 개선할 수 있는 몇 가지 지점들이 보입니다. 또한, 프로덕션 환경에서 잠재적인 크래시를 유발할 수 있는 fatalError 사용에 대한 검토가 필요합니다.

* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

persistentContainer 초기화 실패 시 fatalError를 호출하여 앱을 강제 종료하고 있습니다. 프로덕션 환경에서는 앱이 크래시되는 대신, 오류를 기록하고 fallback UI를 보여주는 등 더 안정적인 방식으로 오류를 처리해야 합니다. 이는 앱 안정성에 치명적인 영향을 줄 수 있습니다.

                // fatalError()는 프로덕션 앱에서 사용하면 안됩니다.
                // 오류 로깅 및 사용자에게 알림 등 안정적인 오류 처리 방식으로 대체해야 합니다.
                print("Unresolved error \(error), \(error.userInfo)")
References
  1. 런타임 크래시를 현실적으로 유발할 수 있는 강제 언래핑 또는 강제 종료는 P0(가장 높은 심각도)로 분류해야 합니다. fatalError는 의도적인 크래시를 유발하므로 이 규칙에 해당합니다. (link)

// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

saveContext에서 오류 발생 시 fatalError를 호출하고 있습니다. 데이터 저장 실패가 앱 전체를 중단시켜야 할 만큼 심각한 오류는 아닐 수 있습니다. 이 부분도 크래시 대신 오류 로깅 등으로 대체하여 앱의 안정성을 높이는 것이 좋습니다.

                let nserror = error as NSError
                // fatalError()는 프로덕션 앱에서 사용하면 안됩니다.
                // 오류 로깅 등 안정적인 오류 처리 방식으로 대체해야 합니다.
                print("Unresolved error \(nserror), \(nserror.userInfo)")
References
  1. 런타임 크래시를 현실적으로 유발할 수 있는 강제 언래핑 또는 강제 종료는 P0(가장 높은 심각도)로 분류해야 합니다. fatalError는 의도적인 크래시를 유발하므로 이 규칙에 해당합니다. (link)

}

class APIService {
static let share = APIService()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

APIService를 싱글톤으로 구현하면 사용하기는 편리하지만, 다른 객체들이 이 싱글톤에 직접 의존하게 되어 강한 결합(tight coupling)이 발생합니다. 이는 단위 테스트를 어렵게 만들고 유연성을 떨어뜨립니다. 의존성 주입(Dependency Injection)을 사용하여 APIService 인스턴스를 외부에서 생성하고 주입하는 방식으로 변경하는 것을 고려해 보세요.

References
  1. 의미 있는 단위 테스트를 어렵게 만드는 강한 결합을 도입하는 구조적 설계는 P1(높은 심각도)로 분류해야 합니다. 싱글톤 패턴은 의존성 주입을 방해하여 이 규칙에 위배될 수 있습니다. (link)

Comment on lines +39 to +42
init() {
self.vm = MainViewModel()
super.init(nibName: nil, bundle: nil)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

MainViewControllerMainViewModel을 직접 생성하고 있습니다. 이는 View와 ViewModel 간의 강한 결합을 만들어 테스트(예: Mock ViewModel 사용)를 어렵게 만듭니다. 의존성 주입 패턴을 적용하여 init을 통해 외부에서 ViewModel을 주입받도록 수정하는 것이 좋습니다. 또한, vm 프로퍼티를 let으로 선언하여 불변성을 보장할 수 있습니다.

Suggested change
init() {
self.vm = MainViewModel()
super.init(nibName: nil, bundle: nil)
}
init(viewModel: MainViewModel) {
self.vm = viewModel
super.init(nibName: nil, bundle: nil)
}
References
  1. 의미 있는 단위 테스트를 어렵게 만드는 강한 결합을 도입하는 구조적 설계는 P1(높은 심각도)로 분류해야 합니다. 뷰 컨트롤러가 뷰 모델을 직접 생성하는 것은 강한 결합의 예입니다. (link)

Comment on lines +142 to +152
guard let self else { return nil }

let section = self.dataSource.snapshot().sectionIdentifiers
let sectionType = section[sectionIndex]

switch sectionType {
case .spring, .autumn:
return self.cardSection()
case .summer, .winter:
return self.listSection()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

createLayout 클로저 내부에서 dataSource의 스냅샷에 직접 접근하여 섹션 타입을 결정하고 있습니다. 이는 레이아웃 로직이 데이터 소스의 현재 상태(특히 섹션 순서)에 의존하게 만들어 코드를 취약하게 만듭니다. 예를 들어, 나중에 스냅샷에서 섹션 순서를 변경하면 의도치 않게 레이아웃이 변경될 수 있습니다. 레이아웃은 sectionIndex를 기반으로 SeasonKeyword.allCases와 같은 정적인 소스에서 섹션 타입을 결정하도록 하여 데이터 소스와의 결합도를 낮추는 것이 좋습니다.

            guard let self else { return nil }
            
            let sectionType = SeasonKeyword.allCases[sectionIndex]
            
            switch sectionType {
            case .spring, .autumn:
                return self.cardSection()
            case .summer, .winter:
                return self.listSection()
            }
References
  1. 의미 있는 단위 테스트를 어렵게 만드는 강한 결합을 도입하는 구조적 설계는 P1(높은 심각도)로 분류해야 합니다. 레이아웃 로직이 데이터 소스의 동적 상태에 의존하는 것은 강한 결합의 한 형태입니다. (link)

}

// API 호출
return APIService.share.fetch(url: url)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

MainViewModelAPIService.share 싱글톤을 직접 참조하고 있습니다. 이는 ViewModel과 Service 간의 강한 결합을 유발하여, APIService의 구현을 교체하거나 MainViewModel을 독립적으로 테스트하기 어렵게 만듭니다. APIService를 프로토콜로 추상화하고, init을 통해 의존성을 주입받는 구조로 변경하는 것을 권장합니다.

References
  1. 의미 있는 단위 테스트를 어렵게 만드는 강한 결합을 도입하는 구조적 설계는 P1(높은 심각도)로 분류해야 합니다. 뷰 모델이 서비스 싱글톤을 직접 사용하는 것은 이 규칙에 위배됩니다. (link)

devBambu added a commit that referenced this pull request Mar 17, 2026
Revert "✨ Feat: 검색 기능 구현 (UI 제외)"
Comment on lines +56 to +61
return Observable.merge(results)
//scan 을 이용하여 스트림의 결과값들을 딕셔너리 형태로 누적
.scan(into: [SeasonKeyword : [Music]]()) { partial, element in
let (keyword, musics) = element
partial[keyword] = musics
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

딕셔너리 형태로 결과값을 가져오는 이유가 궁금합니다! 스냅샷 설정을 위해서인가요??

Comment on lines +11 to +20
enum SeasonKeyword: String, CaseIterable, Hashable {
case spring = "봄"
case summer = "여름"
case autumn = "가을"
case winter = "겨울"

var title: String {
rawValue
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rawValue를 String으로 미리 선언해서 사용하는 방법이 가독성이 좋네요

let update: Observable<[SeasonKeyword : [Music]]>
}

func transform(input: Input) -> Output {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch 메서드에서는 오류 발생시 observer(.failure(error))로 전달하지만, 호출 받아서 처리하는 transfrom 메서드에서는 처리하는 로직이 존재하지 않습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러처리 관련해서 어떻게 처리해야하는지 생각을 못했네요.
감사합니다!

- 상단 검색 바 기능 구현
- 검색 결과 화면 설계 및 구현
yy-ss99 added a commit that referenced this pull request Mar 19, 2026
Added detailed project description, goals, and features for the iOS app inspired by Apple Music.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants