Skip to content

"정말 감명깊게 본 영화는 마치 무늬처럼 우리 몸 속에 남더라고"

Notifications You must be signed in to change notification settings

seosieve/MoonyIOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 

Repository files navigation

MOONY - 취향들이 새겨져 만들어지는 무늬

A Pattern Carved With Tastes









프로젝트 소개

🎥 "정말 감명깊게 본 영화는 무늬처럼 우리 몸안에 남더라고"

실시간 영화 순위를 살펴보고, 궁금했던 영화를 검색, 메모와 함께 기억하고싶은 영화들을 소중하게 저장할 수 있는 앱


프로젝트 주요 기능

  • 날마다의 영화 순위, 관객 수를 살펴볼 수 있는 메인화면
  • 줄거리와 등장인물 정보 등을 확인할 수 있는 상세화면
  • Youtube와 연동되어 제공되는 영화 예고편
  • 장르별 개봉 영화 탐색, 개봉일 순, 관객 기대 순위 순 정렬 기능
  • TMDB 데이터베이스를 이용한 영화 검색 / 정렬 / 저장 기능
  • 원하는 영화를 메모와 함께 나의 폴더별 무늬 저장소에 저장 가능

프로젝트 개발 환경

  • 개발 인원
    • iOS개발 1명
  • 개발 기간
    • 2024.07 - 2024.08 (1개월)
  • iOS 최소 버전
    • iOS 16.0+

프로젝트 기술 스택

  • 활용기술 및 키워드

    • iOS : swift 5.10, xcode 15.4, UIKit
    • Network : RxSwift, Alamofire
    • UI : CodeBaseUI, Snapkit, Then, Hero
  • 라이브러리

라이브러리 사용 목적 Version
Kingfisher 이미지 처리 7.0
SkeletonView 로딩 이미지 처리 1.31
Realm 앱 내 파일 저장소 10.52.1

프로젝트 아키텍처


Input / Output + MVVM Architecture

  • RxSwift Input / Output Pattern을 통한 양방향 데이터 바인딩으로 프로젝트 데이터 흐름 일원화
  • Router Pattern을 통한 반복되는 네트워크 작업 추상화, RxSwift Single Traits를 통한 에러 핸들링
  • Generic 형식의 Base Class 상속과 Protocol 채택을 통한 프로젝트 구조의 명시적 정의

트러블 슈팅

1. 네트워크 통신에서 onError를 받게 되면 Control Event의 전체 Stream이 끊기는 문제

onNext, onComplete가 아닌 onError를 받게 되면 FlatMap에서 Mapping한 함수일지라도 Stream 전체가 끊겨버린다.


  • Request와 Decodable 리턴 형식만 다른 반복되는 네트워크 통신을 추상화하기 위해, Alamofire를 Router Pattern과 함께 적용
  • RxSwift의 Single Traits를 사용하여 Observable의 이벤트 방출, 완료를 단순화하여 처리하려 하였으나, Error Case시에 전체 Stream까지 모두 종료되는 이슈가 발생
  • Result 형태를 맵핑하는 Single을 반환값으로 설정하고, Error Case 또한 Single에서가 아닌 Result 형태에서 처리하는 형태로 문제 해결

Network Manager

//Network Request with RxSwift
func rxNetworkRequest<T: Decodable>(router: Network, type: T.Type) -> Single<T> {
    //Create Observable
    let observable = Single<T>.create { single in
        //Mapping Alamofire
        AF.request(router.endPoint, method: router.method, parameters: router.parameters).responseDecodable(of: T.self) { response in
            switch response.result {
            case .success(let value):
                single(.success(value))
            case .failure(let error):
                single(.failure(error))
            }
        }
        //Return Disposable
        return Disposables.create()
    }
    return observable
}

ViewModel

button.rx.tap
    .throttle(.seconds(1), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { NetworkManager.shared.networkRequest(router: Network.poster(id: id), type: PosterResult.self) }
    .subscribe(onNext: { value in
        switch value {
        case .success(let poster):
            print(poster)
        case .failure(let error):
            print(error)
        }
    }, onError: { error in
        print(error)
    }, onCompleted: {
        print("Completed")
    }, onDisposed: {
        print("Disposed")
    })
    .disposed(by: disposeBag)

2. MVVM Architecture에서 항상 반복해서 Class와 Method를 선언해주어야 하는 문제

loadView, Input, Output, transform 등의 작업을 Class 생성 시마다 매 번 반복을 해 주어야 한다.


  • MVVM 구조에서 반복적으로 수행되는 init, loadView, configure 등의 작업들을 자동화 해주기 위해 Generic 형식의 Base Class 상속을 활용
  • ViewModel에서 Input \ Output 구조체, 구조체를 바인딩해주는 transform메소드의 구현을 명시적으로 정의해주기 위해 Protocol의 AssociatedType 사용

Base View Controller

class BaseViewController<View: BaseView, ViewModel: BaseViewModel>: UIViewController {
    
    let baseView: View
    let viewModel: ViewModel
    
    init(view: View, viewModel: ViewModel) {
        self.baseView = view
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        self.view = baseView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()
        configureRx()
    }
    
    func configureView() { }
    
    func configureRx() { }
}

View Model Protocol

protocol ViewModelType {
    
    associatedtype Input
    
    associatedtype Output
    
    func transform(input: Input) -> Output
}

About

"정말 감명깊게 본 영화는 마치 무늬처럼 우리 몸 속에 남더라고"

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages