Search
🚀

Result 타입 (심화) - 응용해보기

안녕하세요 iOS 개발자 루크입니다
오늘은 예외처리의 1등 공신 Result 타입을 가지고 여러가지 방식으로 응용할 수 있는 케이스들을 배워보겠습니다.
먼저 앞선 포스팅을 보고 오시면 더 이해에 도움이 됩니다.
우리는 앞선 포스팅에서 Result 타입이 왜 등장했고 어떤 장점이 있는지, 또 어떻게 사용하는 지 알아보았습니다.
그럼 이제 Result 타입을 더 어떻게하면 응용할 수 있을지 다양한 경우의 수를 배워보겠습니다.

발생가능한 에러를 익명화하기

앞선 포스팅에서 우리는 Result 타입을 활용하면 에러타입을 유형화해서, 발생가능한 에러타입을 호출부에 전달함으로써 핸들링해야할 에러의 종류를 규정할 수 있었습니다. 하지만 모든 에러를 유형화할 수는 없겠죠
유형화하기 힘든 API 사용시, NSError 를 사용해 에러를 익명화 할 수 있다. 즉, 모든 에러 유형을 포괄하겠다는 말.
모든 Swift 의 Error 가 자동으로 NSError 변환가능합니다.
타입 캐스팅도 실패하지 않습니다. do 클로져 안에서 던져진 모든 에러를 NSError 로 변환할 수도 있어요
class ImageProcessor { typealias Handler = (Result<UIImage, NSError>) -> Void func process(_ image: UIImage, then handler: @escaping Handler) { do { // Any error can be thrown here var image = try transformer.transform(image) image = try filter.apply(to: image) handler(.success(image)) } catch let error as NSError { // NSError 를 사용하면 던져진 error 를 모두 NSError 인스턴스로 변환한다. handler(.failure(error)) } } }
Swift
복사
catch 블럭 안에는 항상 NSError 가 들어가게 됩니다. 이는 던져진 error 의 종류와는 상관이 없습니다.

Result 의 get() 메서드 활용

결론부터 이야기하자면, Result 에 내장되어 있는 get() 메서드를 사용하면, 값에 바로 접근이 가능합니다. switch 문 없이요 ㅎ
extension Result { func get() throws -> Value { switch self { case .success(let value): return value case .failure(let error): throw error } } }
Swift
복사
한번 사용해볼까요?
class SearchResultsLoaderTests: XCTestCase { func testLoadingSingleResult() throws { let engine = NetworkEngineMock.makeForSearchResults(named: ["Query"]) let loader = SearchResultsLoader(networkEngine: engine) var result: Result<[SearchResult], SearchResultsLoader.Error>? loader.loadResults(matching: "query") { result = $0 } let searchResults = try result?.get() //😎 XCTAssertEqual(searchResults?.count, 1) XCTAssertEqual(searchResults?.first?.name, "Query") } }
Swift
복사
if 문과 같은 분기점을 만들 필요가 없기 때문에, 테스트 코드를 작성하는 데 매우 용이합니다. 다만 에러를 발생시킬 수 있기 때문에, try 문과 함꼐 사용하여야 합니다.
성공, 실패에 대한 분기점을 만들 필요가 없다.

JSON 디코딩 메서드 확장해보기

JSON 데이터를 디코딩 하는데에도, 익스텐션을 추가하면 반복되는 동작을 편리하게 처리할 수 있어요
// Here we're using 'Success' as the name for the generic type // for our result's value (rather than 'Value', like we did // before). This is to match Swift 5's naming convention. extension Result where Success == Data { func decoded<T: Decodable>(using decoder: JSONDecoder = .init()) throws -> T { let data = try get() return try decoder.decode(T.self, from: data) } }
Swift
복사
마찬가지로 사용해봅시다.
load { [weak self] result in do { let user = try result.decoded() as User self?.userDidLoad(user) } catch { self?.handle(error) } }
Swift
복사
switch 문을 사용하지 않고도 유려하게 디코딩을 시도, 값을 리턴하는 모습입니다!
이외에도,
map, mapError, flatMap 으로
Result 타입을 유지하면서 데이터만 전처리할 수 있는 메서드도 Result 타입내에 미리 구현되어 있습니다.
이를 잘만 활용하면 Result 타입을 더 Result 타입 답게 편리하게 사용할 수 있을 것 같네요.
오늘 포스팅은 여기서 마치도록 하겠습니다 감사합니다