✨Feat: 검색 화면 구현 및 AVPlayer 재생 최적화 (ReactorKit 구조 개선)#13
Conversation
1. 컨테이너 뷰 Leading 앞으로 10만큼 당기기 2. 구분선 trailing 앞으로 10만큼 당기기
600x600은 너무 무거워서 로딩 속도 저하됨
평균 색상으로 카드 만드는 기능 제거
There was a problem hiding this comment.
Code Review
이번 PR은 검색 화면 구현과 AVPlayer 최적화 등 많은 기능 추가와 개선이 이루어진 점이 인상적입니다. 특히 ReactorKit 구조를 개선하고 데이터 흐름을 명확히 하려는 노력이 돋보입니다.
다만, HomeViewController에서 검색 관련 의존성을 직접 생성하는 부분은 아키텍처적으로 개선이 필요해 보입니다. 의존성 주입(DI) 원칙을 적용하여 HomeViewController의 책임을 줄이고 테스트 용이성을 높이는 방향으로 리팩토링하는 것을 제안합니다. 이 부분을 수정하면 전반적인 코드 구조가 더욱 견고해질 것입니다. 자세한 내용은 코드 리뷰 코멘트를 참고해주세요.
| private lazy var searchController: UISearchController = { | ||
| let repository = SearchRepository() | ||
| let searchUseCase = SearchUseCase(repository: repository) | ||
| let searchReactor = SearchReactor(searchUseCase: searchUseCase) | ||
|
|
||
| let searchVC = SearchViewController() | ||
|
|
||
| searchVC.reactor = searchReactor | ||
|
|
||
| searchVC.loadViewIfNeeded() | ||
|
|
||
| let controller = UISearchController(searchResultsController: searchVC) | ||
| controller.searchBar.placeholder = "M/V, music, podcast 검색" | ||
| controller.obscuresBackgroundDuringPresentation = false | ||
| controller.searchBar.autocorrectionType = .no // 자동완성 기능 off | ||
| controller.searchBar.returnKeyType = .search | ||
| return controller | ||
| }() |
There was a problem hiding this comment.
HomeViewController 내에서 SearchRepository, SearchUseCase, SearchReactor, SearchViewController 등 검색 기능에 필요한 여러 의존성을 직접 생성하고 있습니다. 이는 뷰 컨트롤러가 자신의 역할을 넘어 데이터, 도메인, 다른 뷰에 대한 생성 책임까지 갖게 되는 심각한 단일 책임 원칙(SRP) 및 의존성 역전 원칙(DIP) 위반입니다.
이러한 구조는 다음과 같은 아키텍처 문제를 야기합니다:
- 강한 결합도 및 테스트 불가:
HomeViewController가 구체적인 구현 클래스(SearchRepository등)에 직접 의존하여 유연성이 떨어지고, 의존성을 외부에서 교체할 수 없어 단위 테스트가 거의 불가능해집니다. - 아키텍처 경계 붕괴: 프레젠테이션 레이어(View)가 데이터 레이어(
SearchRepository)와 도메인 레이어(SearchUseCase)를 직접 생성하여 레이어 간의 경계가 무너집니다. - 불필요한 객체 생성:
SceneDelegate에서 이미 생성된 관련 객체들이 사용되지 않고 버려지게 됩니다.
개선 제안:
의존성 주입(DI)을 적용하여 SceneDelegate나 별도의 DI 컨테이너에서 모든 의존성을 생성하고, HomeViewController는 필요한 객체를 init을 통해 주입받도록 리팩토링해야 합니다.
예시:
// In SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
// 의존성 생성
let repository = SearchRepository()
let homeUseCase = FetchHomeContentUseCase(repository: repository)
let homeReactor = HomeReactor(fetchHomeContentsUseCase: homeUseCase)
let searchUseCase = SearchUseCase(repository: repository)
let searchReactor = SearchReactor(searchUseCase: searchUseCase)
let searchVC = SearchViewController()
searchVC.reactor = searchReactor
let searchController = UISearchController(searchResultsController: searchVC)
searchController.searchBar.placeholder = "M/V, music, podcast 검색"
// ... 기타 searchController 설정
// 의존성 주입
let homeVC = HomeViewController(reactor: homeReactor, searchController: searchController)
let navigationController = UINavigationController(rootViewController: homeVC)
// ... window 설정
}
// In HomeViewController
final class HomeViewController: UIViewController, View {
private let searchController: UISearchController
init(reactor: HomeReactor, searchController: UISearchController) {
self.searchController = searchController
super.init(nibName: nil, bundle: nil)
self.reactor = reactor
}
// ... configureUI에서 self.searchController 사용
}
ybin4548
left a comment
There was a problem hiding this comment.
Clean Architecture를 도입해서 해당 로직들을 Layer와 기능에 따라 분류한 점이 인상깊습니다. 공부할 수 있는 예제가 생겨서 좋습니다.
🚀 작업 내용
이번 PR에서는 UISearchController를 활용한 검색 화면(SearchViewController) 구현과 상단 뮤직비디오 자동 재생 기능을 작업했습니다.
기능 구현뿐만 아니라, ReactorKit의 단방향 데이터 흐름을 더 명확하게 분리하고 불필요한 상태(State)를 덜어내는 리팩토링도 함께 진행했습니다
🛠 주요 구현 및 리팩토링 사항
ReactorKit 구조 개선:
UI 바인딩 로직 분리: 기존 bind(reactor:) 안에 혼재되어 있던 순수 UI 로직(홈 버튼 dismiss, Stretchy 스크롤 애니메이션 등)을 bindUI() 함수로 완전히 빼서 분리했습니다. 이제 bind(reactor:)에는 진짜 View와 Reactor 간의 소통 코드만 남아서 가독성이 훨씬 좋아졌습니다.
불필요한 State 제거: 한 번 쓰고 마는 keyword 변수를 State에서 과감히 지워버리고 데드 코드를 없앴습니다.
Observable 체인 최적화: 기획상 검색창의 답답함을 줄이기 위해 로딩 스피너를 뺐는데, 이에 맞춰 껍데기만 남아있던 Observable.concat 배열도 제거해서 통신 스트림을 더 가볍게 만들었습니다.
AVPlayer 스트리밍 최적화 (버퍼링 수정)👺:
비디오 URL을 받자마자 무작정 play()를 호출해서 생기던 버퍼링 이슈를 해결했습니다. AVPlayerItem의 status를 Rx로 실시간 observe 하다가, .readyToPlay 상태가 되는 정확한 시점에 영상을 띄우고 재생하도록 수정했습니다. (.take(1)을 써서 불필요한 반복 구독도 막았습니다)
셀 뷰가 그려지는 시점 차이를 고려해, configure 함수 안에서 강제로 레이어 크기를 맞추던 코드를 빼고 오직 layoutSubviews()에서만 frame을 동기화하도록 정리했습니다.
셀 재사용 시 영상 소리가 겹치지 않도록 prepareForReuse에서 player.pause() 및 메모리 초기화 처리를 꼼꼼히 해두었습니다.
UI/UX 디테일:
collectionView.rx.contentOffset의 y 값을 계산해, 화면을 밑으로 당기면 상단 뮤직비디오 셀이 확대되는 Stretchy Video 애니메이션 효과를 넣었습니다.
2026-03-18.3.38.09.mov