Result 타입을 활용해 오류처리 하기(Modern CollectionView)

2024.04.16 - [◽️ Programming/iOS] - iOS Result 타입 (예외처리)

 

iOS Result 타입 (예외처리)

Result 타입 이란? 함수나 메서드에서 성공 또는 실패를 반환하는데 사용되는 형식이며, 주로 비동기 작업 진행 시 사용된다. Swift5 에서 새로 추가된 Result Type를 이해하기 위해선 열거형 , 연관값,

dongdida.tistory.com

 

 

이전에 한번 알아보았던 Result Type에 대해서 다시 한번 짚고 넘어가보자 정의를 한번 다시 정리하고 이번에 Modern CollectionView를 만들었던 영화 정보를 가져오는 방식에 적용해보려고 한다!

 

먼저 Result 타입은 Swift5.0에서 새로 추가된 에러처리 방법이다. 에러가 발생하는 경우 에러를 따로 외부로 던지는 것이 아니라 리턴 타입을 리절트 타입으로 구현해서 함수 실행의 성공과 실패를 담아 리턴한다.

 

이를 통해서 do catch 를 사용해서 에러를 처리하는 방식보다 훨씬 가독성과 유지보수성을 향상시킬 수 있고 비동기 작업에서 오류 발생할 가능성이 적다.

 

정의는 이정도로 알아보고 이제 이전에 영화 정보 API를 가져와 컬렉션 뷰에 적용했던 방식에서 한번 알아보자

import Foundation
import RxSwift

class ViewModel {
    let disposeBag = DisposeBag()
    private let tvNetwork: TVNetwork
    private let movieNetwork: MovieNetwork
    
    init() {
        let provider = NetworkProvider()
        tvNetwork = provider.makeTVNetwork()
        movieNetwork = provider.makeMovieNetwork()
    }
    
    struct Input {
        let tvTrigger: Observable<Void>
        let movieTrigger: Observable<Void>
    }
    
    struct Output {
        let tvList: Observable<[TV]>
        let movieResult: Observable<Result<MovieResult, Error>>
    }
    
    func transform(input: Input) -> Output {
        let tvList = input.tvTrigger.flatMapLatest {[unowned self] _ -> Observable<[TV]> in
            return self.tvNetwork.getTopRatedList().map{ $0.results }
        }
        
        let movieResult = input.movieTrigger.flatMapLatest { [unowned self] _ -> Observable<Result<MovieResult, Error>> in
            return Observable.combineLatest(self.movieNetwork.getUpcomingList(), self.movieNetwork.getPopularList(), self.movieNetwork.getNowPlayingList()) { upcoming, popular, nowPlaying -> Result<MovieResult, Error> in
                    .success(MovieResult(upcomming: upcoming, popular: popular, nowPlaying: nowPlaying))
            }.catch { error in
                return Observable.just(.failure(error))
            }
        }
        return Output(tvList: tvList, movieResult: movieResult)
    }
}

RxSwift를 활용해 OutPut으로 영화 버튼을 클릭해 영화 정보를 가져오는 시점에서 API 정보를 제대로 가져오는지, 오류가 있다면 앱이 종료되지 않고 에러 메세지를 남길 수 있도록 Result 타입을 활용해 구현해보았다.

struct Output {
    let tvList: Observable<[TV]>
    let movieResult: Observable<Result<MovieResult, Error>>
}

먼저 아웃풋 부분에 영화 정보를 클릭하게 되면 MovieResult 의 값을 가져와 데이터를 뿌려주는데 해당 값이 오류가 있다면 Error를 표현할 수 있도록 구현했다.

let movieResult = input.movieTrigger.flatMapLatest { [unowned self] _ -> Observable<Result<MovieResult, Error>> in
    return Observable.combineLatest(self.movieNetwork.getUpcomingList(), self.movieNetwork.getPopularList(), self.movieNetwork.getNowPlayingList()) { upcoming, popular, nowPlaying -> Result<MovieResult, Error> in
            .success(MovieResult(upcomming: upcoming, popular: popular, nowPlaying: nowPlaying))
    }.catch { error in
        return Observable.just(.failure(error))
    }
}

이렇게 리턴 값에 리절트 타입을 넣어줌으로써 정상적으로 데이터를 받게된다면 .success 에 값이 들어가고 오류가 있다면 .just를 활용해 오류 값을 넣어주면 된다.

output.movieResult.bind { [weak self] movieResult in
print("Movie Result \\(movieResult)")
switch movieResult {
case .success(let movieResult):
    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    let bigImageList = movieResult.nowPlaying.results.map { movie in
        return Item.bigImage(movie)
    }
    let bannerSection = Section.banner
    snapshot.appendSections([bannerSection])
    snapshot.appendItems(bigImageList, toSection: bannerSection)
    
    let horizontalSection = Section.horizontal("Popular Movies")
    let normalList = movieResult.popular.results.map { movie in
        return Item.normal(Content(movie: movie))
    }
    snapshot.appendSections([horizontalSection])
    snapshot.appendItems(normalList, toSection: horizontalSection)
    
    let upcomingSection = Section.vertical("UpComing Moives")
    let upcomingList = movieResult.upcomming.results.map { movie in
        return Item.list(movie)
    }
    
    snapshot.appendSections([upcomingSection])
    snapshot.appendItems(upcomingList, toSection: upcomingSection)
    
    self?.dataSource?.apply(snapshot)
case .failure(let error):
    print(error)
}

그 다음 VM에서 Result Type으로 output을 구성해주었기 때문에 VC에서 데이터를 바인드 할 때 해당 값을 그대로 사용하여 성공적으로 데이터가 전달되었다면 snapshot을 통해 diffabledatasource를 받아 정상적으로 구현이 가능해지며,

 

.failure를 통해 값이 정상적으로 받아지지 않는다면 error를 호출할 수 있게 된다.

 

이토록 Result 타입을 통해 에러를 관리해주면 훨씬 수월하고 가독성있게 에러를 관리 해줄 수 있다.!

오늘은 여기까지!