Search
🛬

Result 이란? + 사용법

안녕하세요 iOS 개발자 루크입니다
오늘은 예외처리의 1등 공신 Result 타입에 대해서 배워보도록 하겠습니다.

Result 타입이란?

Result 타입의 장점을 이해하기 위해서는 먼저 그 이전의 코드를 작성하는 방식을 이해하여야 합니다.
이전의 문제 상황을 해결하기위해 등장한 놈이기 때문이죠!

문제 상황

Result 타입이 등장하게된 배경은 아래와 같이 Completion Handler 를 작성하는 상황입니다.
let url = URL(string: "https://www.swiftbysundell.com")! let task = URLSession.shared.dataTask(with: url) { data, response, error in // 2개 이상의 optional 타입 if let error = error { // 에러 핸들링 ... } else if let data = data { // 데이터 존재시 데이터 핸들링 ... } } task.resume()
Swift
복사
위와 같이 이전에는 compltion handler 를 호출하는 경우에, 성공, 실패를 표현하기 위해 두개의 optional 한 값을 다루었다고 하네요.
여기에 두가지 문제가 있습니다.
첫번째는 바로, 성공, 실패를 모델링하기 위해 만들어졌지만, 2가지가 아닌 4가지 경우의 수가 존재한다는 것입니다.
데이터가 존재하며, 에러도 존재하는 경우
데이터가 존재하며, 에러가 존재하지 않는 경우
데이터가 존재하지 않으며, 에러가 존재하는 경우
데이터가 존재하지 않으며, 에러가 존재하지 않는 경우
성공, 실패를 모델링하고자 했는데 그 이상의 case 가 존재하게 됩니다.
두번째 문제점은 값을 사용할 때마다, optional 타입을 unwrap 해줄 필요가 있다는 것입니다.( 귀찮아요…)
따라서 Swift 의 Result 타입을 사용하면, 이러한 문제점들을 한번에 해결할 수 있습니다.
자 그럼 Result 타입을 만나러 가봅시다.

Result 정의부

Result 타입의 장점을 다시 이야기 하면서 가보죠
결과의 성공, 실패 여부와는 상관없이 하나로 결과를 표현할 수 있게끔 도와줌. → 가독성 증가
optional 타입을 사용하지 않기 때문에, unwrap 을 위한 코드를 작성하지 않아도 됨. → 코드 길이 단축
정의되어 있는 모습은 다음과 같습니다.
enum Result<Success, Failure> where Failure: Error { case success(Success) case failure(Failure) }
Swift
복사
제네릭 타입으로 작성되어 있기 때문에, 어떠한 타입에든 사용이 가능합니다.
먼저 URLSessionResult<Data, Error> 를 passing 하는 새 API 를 작성하여 확장해보죠
extension URLSession { typealias Handler = (Result<Data, Error>) -> Void func dataTask( with url: URL, handler: @escaping Handler) -> URLSessionDataTask { dataTask(with: url) { data, _, error in if let error = error { handler(.failure(error)) } else { handler(.success(data ?? Data())) } } } }
Swift
복사
자 이제 URLSession 의 유려해진 코드를 다시 확인해보죠
let task = URLSession.shared.dataTask(with: url) { result in switch result { case .success(let data): // Handle successful response data ... case .failure(let error): // Handle error ... } }
Swift
복사
훨씬 가독성이 좋아진 거 보이시나요?.
성공이면, 성공인 case 로
실패면, 실패인 case 로
누가봐도 분명해보이죠
또한 optional 값을 바인딩하는 코드도 보이지 않습니다.
이처럼 Result 타입을 사용하면, 코드가 더 읽기 쉬워진다는 장점이 있습니다.

또 한가지 장점 - 협업시 발생 가능한 에러타입 암시

협업을 하다보면은 Swift 의 에러 핸들링 방식인 throw 를 사용해서, 에러를 발생시킬 수 있음을 알린다고 해도 함수를 호출하는 사람은 에러가 발생한다는 사실만 알 수 있을 뿐, 어떠한 에러가 발생가능한지는 함수의 정의부를 확인해야 알 수 있다는 불편함이 있었습니다.
하지만 Result 타입을 사용하면, 유형화된 에러타입을 가지는 경우, 이 타입이 분명히 호출하는 사람이 확인할 수 있기 때문에 switch 문을 사용해서 모든 case 를 핸들링할 수 있게 돼요
예시와 함께 봅시다.
enum ImageLoadingError: Error { case networkFailure(Error) case invalidData }
Swift
복사
위는 이미지를 불러오는 데 발생가능한 에러들의 유형을 명시한 직접 만든 에러타입니다.
하나는 네트워크 에러이고
하나는 다운로드한 데이터가 유효하지 않은 경우이죠
Result 타입을 위에서 만든 에러타입을 사용해 구체화 해주기만 하면 됩니다.
이렇게 하면 호출부에 발생할 수 있는 에러에 대한 더 많은 정보를 담아 보낼 수 있다.
struct ImageLoader { typealias Handler = (Result<UIImage, ImageLoadingError>) -> Void var session = URLSession.shared func loadImage(at url: URL, then handler: @escaping Handler) { let task = session.dataTask(with: url) { result in switch result { case .success(let data): if let image = UIImage(data: data) { handler(.success(image)) } else { handler(.failure(.invalidData)) } case .failure(let error): handler(.failure(.networkFailure(error))) } } task.resume() } }
Swift
복사
위처럼 디자인된 API 는 잠재적인 각각의 에러에 대해서 빠짐없이 핸들링할 수 있게 됩니다.
let imageURL = URL(string: "https://www.swiftbysundell.com/images/logo.png")! let imageLoader = ImageLoader() imageLoader.loadImage(at: imageURL) { result in switch result { case .success(let image): // Handle image ... case .failure(.invalidData): // Handle an invalid data failure ... case .failure(.networkFailure(let error)): // Handle any network error ... } }
Swift
복사
아무래도 에러타입이 명확하니까, 발생가능한 에러를 더 빠르게 확인하고 적절하게 action 을 취해줄 수 있겠죠?
오늘 포스팅은 여기서 마치겠습니다 ~,~
해당 포스팅은 아래 포스팅으로 이어집니다.

참고