안녕하세요 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 타입 답게 편리하게 사용할 수 있을 것 같네요.
오늘 포스팅은 여기서 마치도록 하겠습니다 감사합니다