-
Swift - Result TypeProgramming/Swift 2022. 3. 30. 20:07
안녕하세요 BeePeach입니다 :)
오늘 공부해볼 내용은 Swift5부터 새롭게 추가된 Error handling 방식인 Result입니다.
(작성하다가 다 날아가서 다시 작성하는 ㅠㅠ)
Result를 이해하기 위해서는 Enumeration과 Associated Value 그리고 Generic을 정확하게 이해하고 있어야 합니다.
기존에 사용하던 throws는 이 함수, 메서드, 클로저, 생성자가 Error를 던질 수 있다는 것을 나타냈지만 어떤 형식의 Error가 전달될지는 알려주지 않았습니다.
그래서 do-catch문에서 Error를 처리할 때 패턴 매칭을 이용하거나 타입 캐스팅을 하는 방식으로 Error를 특정한 뒤에 처리해야 했습니다.
이 방식이 완전 나쁜 방식은 아니지만 문제는 컴파일 타임에 형식을 할 수 없다는 큰 단점이 있습니다.
그래서 만약 catch로 처리하지 못한 Error가 있다면 crash가 발생하는 경우도 종종 있습니다.
반면에 Result Type은 Error의 타입을 특정할 수 있기 때문에 컴파일 시점에 알 수 있게 만들 수 있습니다.
물론 do-catch처럼 Error 프로토콜 타입으로 처리할 수도 있습니다.
그리고 throws 키워드를 사용하지 않고 Result를 리턴하는 메서드를 만들어서 Error를 발생시킬 수도 있습니다.
그럼 Result는 어떠한 장점을 가지고 있는지 같이 공부해보도록 하겠습니다.
Result Type
Result는 Generic Enum으로 선언되어있습니다.
Enum case에는 success와 failure가 있고 모두 associated value(연관 값)을 가지고 있습니다.
그리고 타입 파라미터로 Sucess와 Failure를 받습니다.
Success는 모든 타입이 가능하지만 Failure는 Error 프로토콜 타입만 가능합니다.
성공하면 success case의 연관 값에 데이터를 담으면 됩니다.
실패하면 failure case의 연관 값에 Error를 담으면 됩니다.
간단하죠? 그럼 Result 인스턴스를 생성하는 방법부터 보겠습니다.
Result 생성하기
Result는 enum이기 때문에 Type이죠?
그러므로 인스턴스를 생성자로 생성합니다.
create를 파라미터로 받는 생성자를 제공합니다.
파라미터로 throws 클로저를 받습니다.
이 클로저에서 바로 try로 호출하면 됩니다.
여기서 NumberError를 선언했습니다.
그리고 check(number)라는 throwing func을 정의했습니다.
0보다 작다면 NumberError.negativeNumber
100보다 크다면 NumberError.largeNumber(over:) Error를 throw하는 함수입니다.
먼저 do-catch로 이 함수를 처리하는 방법을 먼저 보겠습니다.
do-catch를 이용하면 이런 식으로 사용할 수 있겠죠??
check(number:) 함수가 Error를 던진다면 catch로 Error가 전달되는데 이때 Error의 타입은 Error입니다.
NumberError가 아니죠!
그래서 사용할 때 패턴 매칭이나 타입 캐스팅을 이용해서 경우에 맞게 처리를 해줘야 합니다.
물론 그냥 catch로 Error의 종류를 따지지 않고 모두 처리해버릴 수도 있죠.
그럼 이번엔 Result 인스턴스를 생성해서 처리해보도록 하겠습니다.
Result에서 제공하는 생성자를 이용해서 클로저에 check(number:) throwing 함수를 호출했습니다.
check(number:)가 성공하면 Int를 리턴하고 실패하면 Error를 던지기 때문에 Type이 Result<Int, Error>가 됩니다.
(Result 인스턴스를 생성할 때 NumberError로 Error를 지정하면 에러가 발생합니다. 이 부분에 대해서는 공부가 조금 더 필요할 거 같습니다.)
그럼 여기서 Result를 사용하면 Error의 Type을 특정할 수 있다면서 아니잖아 똑같은데?? 라고 생각할 수 있습니다.
Error의 Type을 특정하는 방법은 바로 뒤에서 살펴보도록 하겠습니다.
그럼 이렇게 처리하면 do-catch와 뭐가 다를까요?
Error의 처리 시점이 달라졌습니다.
do-catch에서는 Error처리 시점이 try를 이용해 throws 함수를 호출할 때 이루어집니다.
하지만 Result를 이용하면 Error처리 시점이 try를 이용해 함수를 호출할 때가 아닙니다.
그 결과를 처리할 때 Error를 처리하게 됩니다.
그리고 처리하는 방법이 조금 달라졌습니다.
Result는 enum이기 때문에 switch로 처리가 가능합니다.
그래서 이렇게 case별로 나누어서 처리할 수 있습니다.
success라면 try의 실행결과가 연관 값에 담깁니다.
실패하면 error가 연관 값에 담깁니다.
그럼 이제 Result에 다른 용도에 대해서 살펴보도록 하겠습니다.
Result를 리턴하는 함수
throws를 이용해서 Error를 throw 하는 함수 대신 Result를 리턴하는 함수를 만들 수 있습니다.
바로 만들어보도록 하겠습니다.
이전 check(number:) 함수의 리턴 타입을 Result<Int, NumberError>로 변경했습니다.
그 이유는 check(number:)의 리턴 타입이 Int였고 Error가 발생하면 NumberError를 throw 했었기 때문입니다.
이렇게 변경하면 이제 throws 키워드는 필요 없어집니다.
왜냐하면 실패할 경우 Error를 throw 하지 않고 failure case의 연관 값에 Error를 담아서 리턴하기 때문입니다.
throw 키워드를 사용하지 않으니 throws키워드도 필요 없어졌습니다.
코드를 살펴보도록 하겠습니다.
0 보다 작다면 NumberError에 negativeNumber Error를 Result의 failure case에 연관 값에 담아 리턴해야 합니다.
발생할 Error가 NumberError라는 게 특정이 됐기 때문에 생략하고 .negativeNumber로 작성할 수 있습니다.
비슷한 예시로 1000보다 클 경우 Error를 담아서 리턴해야 하는데 리턴 타입이 Result인 것을 알고 있죠?
그렇기 때문에 Line 7처럼 Result도 생략이 가능합니다.
코드가 정상적으로 흘러갔다면 성공입니다.
그럼 success case에 원하는 데이터를 연관 값으로 담아서 리턴하면 됩니다.
Result가 제네릭이기 때문에 정의할 때 타입을 명시해야 합니다.
그렇기 때문에 Error가 어떤 형식인지 컴파일 타임에 확인할 수 있습니다.
그럼 이제 이 함수를 어떻게 사용하는지 보도록 하겠습니다.
checkWithResult(number:) 함수를 호출할 때 try? 또는 do-catch를 사용하지 않아도 됩니다.
throws 키워드가 없기 때문이죠.
그리고 결과를 처리할 때 이제 Error의 타입이 NumberError인 것을 확인할 수 있습니다.
정리
이렇게 Result를 이용하면 throws 키워드를 사용하지 않고 Error를 전달할 수 있습니다.
그럼 사용할 때 try 또는 do-catch를 사용하지 않아도 되겠죠?
그리고 Error의 Type을 컴파일 시점에 명확하게 알 수 있게 됩니다.
마지막으로 do-catch문에서는 do에서 throws함수를 호출하고 catch에서 Error를 처리했습니다.
즉 throws 함수를 호출할 때 Error를 처리해주어야 합니다.
그런데 Result는 호출할 때가 아니라 결과를 사용할 때 에러를 처리하게 됩니다.
Result 인스턴스를 생성할 때 즉 throws함수를 호출할 때 에러 처리를 하지 않고 Result 인스턴스를 참조하고 있다가 결과를 처리할 때 에러를 처리하게 됩니다.
Result에서 제공하는 메서드
Result에서 제공하는 몇 가지 메서드들이 있습니다.
get
Result가 제공하는 get 인스턴스 메서드는 Success 타입을 리턴하는 throws 메서드입니다.
즉 우리가 Result를 리턴하는 함수를 만들었는데 이 함수를 이전에 throws를 포함한 함수처럼 만들 수 있습니다.
이 메서드를 이용해서 do-catch, try?로 에러를 처리할 수 있습니다.
예시를 보도록 하겠습니다.
원래 Result를 리턴하는 함수를 처리할 때는 switch를 이용했습니다.
이때 switch이기 때문에 success만 처리하고 싶다면 failure부분을 break 하거나 default를 이용해야 합니다.
하지만 여기서 Result에서 제공해주는 get 메서드를 이용하면 Optional try와 함께 처리해서 success만 처리하거나 do-catch를 이용해서 에러 처리를 할 수도 있습니다.
그리고 async 한 코드에서 completion handler로 Result를 사용하는 예시를 살펴보도록 하겠습니다.
참고자료
https://developer.apple.com/documentation/swift/result
728x90'Programming > Swift' 카테고리의 다른 글
Swift - Error Handling(do-catch, try?, try!) (0) 2022.03.29 Swift - Error Handling (throws와 rethrows) (0) 2022.03.27 Swift - Error Handling(throws) (0) 2022.03.25 Swift - Error Handling 기초 (0) 2022.03.24 Swift - UserDefaults에 customType 저장하기 (0) 2022.03.01