이번 포스트는 Optional
로 지친 심신을 쉬어가는 과정으로 가볍게 Result
타입에 대해 해부해보려고 합니다!
해당 코드는 swift > stdlib > public > core > Result.swift에서 볼 수 있습니다!
@frozen
public enum Result<Success, Failure: Error> {
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
...
각 경우의 관련 값을 포함하여 성공 또는 실패를 나타내는 값입니다.
@frozen
어트리뷰트! 저번에 Optional
에서 다뤘는데, 기억 나시나요?
기억 안 나시는 분들은 한 번 보고 오시는 것도 좋을 거 같습니다! (어트리뷰트만 모아서 정리를 해봐야겠어요..!)
코드를 보면, Result
타입은 열거형이라는 정보를 알 수 있네요!
Success
와 Failure
은 제네릭 타입인데, Failure
은 Error
를 채택한 타입만 가능하게 설정해두었네요!
그리고 각 타입은 success
, failure
케이스에 각각 저장이 됩니다.
Result.map()
@inlinable
public func map<NewSuccess>(
_ transform: (Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
}
}
지정된 변환을 사용하여 성공 값을 매핑하여 새로운 결과를 반환합니다. 성공을 나타내는
Result
인스턴스의 값을 변환해야 할 때 이 방법을 사용하세요.
@inlinable
도 역시 저번에 Optional
에서 다뤘습니다 ㅎㅎ
짧게 복기해보자면! 해당 어트리뷰트는 함수, 메서드, 계산된 속성, 첨자, 편의 초기화 또는 초기화 해제 선언에 적용하여 해당 선언의 구현을 모듈 공개 인터페이스의 일부로 노출합니다.
고차함수 map()
은 내부 값을 다른 타입으로 변환할 때 많이 쓰이는데요! Result
타입에 있는 map()
메서드도 같은 역할을 합니다.
// Result.map() 예시
func getNextInteger() -> Result<Int, Error> { /* ... */ }
let integerResult = getNextInteger()
// integerResult == .success(5)
let stringResult = integerResult.map { String($0) }
// stringResult == .success("5")
Result.mapError()
@inlinable
public func mapError<NewFailure>(
_ transform: (Failure) -> NewFailure
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
지정된 변환을 사용하여 실패 값을 매핑하여 새 결과를 반환합니다. 실패를 나타낼 때
Result
인스턴스의 값을 변환해야 할 때 이 방법을 사용하십시오. 다음 예에서는 결과를 사용자 정의Error
유형으로 래핑하여 결과의 오류 값을 변환합니다.
// Result.map() 예시
struct DatedError: Error {
var error: Error
var date: Date
init(_ error: Error) {
self.error = error
self.date = Date()
}
}
let result: Result<Int, Error> = // ...
// result == .failure(<error value>)
let resultWithDatedError = result.mapError { DatedError($0) }
// result == .failure(DatedError(error: <error value>, date: <date>))
오... Failure
도 map()
으로 변환이 가능하군요!
새로운 사실을 알게 되었네요👍
Result.flatMap()
@inlinable
public func flatMap<NewSuccess>(
_ transform: (Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return transform(success)
case let .failure(failure):
return .failure(failure)
}
}
지정된 변환을 사용하여 성공 값을 매핑하고 생성된 결과를 언래핑하여 새로운 결과를 반환합니다. 변환이 다른
Result
유형을 생성할 때 중첩된 결과를 방지하려면 이 방법을 사용하십시오. 이 예에서는 결과 유형을 반환하는 변환과 함께map
과flatMap
을 사용한 결과의 차이점에 유의하세요.
이 메서드도 고차함수 flatMap
과 같은 기능을 합니다!
// Result.flatMap() 예시
func getNextInteger() -> Result<Int, Error> {
.success(4)
}
func getNextAfterInteger(_ n: Int) -> Result<Int, Error> {
.success(n + 1)
}
let result = getNextInteger().map { getNextAfterInteger($0) }
// result == .success(.success(5))
let result = getNextInteger().flatMap { getNextAfterInteger($0) }
// result == .success(5)
Result.flatMapError()
@inlinable
public func flatMapError<NewFailure>(
_ transform: (Failure) -> Result<Success, NewFailure>
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return transform(failure)
}
}
지정된 변환을 사용하여 실패 값을 매핑하고 생성된 결과를 언래핑하여 새 결과를 반환합니다.
Result.get()
@inlinable
public func get() throws -> Success {
switch self {
case let .success(success):
return success
case let .failure(failure):
throw failure
}
}
}
성공 값을 던지는 표현식으로 반환합니다. 성공을 나타내는 경우 이 결과의 값을 검색하거나, 실패를 나타내는 경우 값을 포착하려면 이 메서드를 사용합니다.
- 매개변수 변환: 인스턴스의 성공 값을 취하는 클로저입니다.
throws
: 클로저 또는 이전.failure
의Result
인스턴스.
// Result.get() 예시
let integerResult: Result<Int, Error> = .success(5)
do {
let value = try integerResult.get()
print("The value is \(value).")
} catch {
print("Error retrieving the value: \(error)")
}
// Prints "The value is 5."
do-catch문
을 활용한 get()
메서드는 저 같은 경우는 잘 안 쓸 거 같네요🧐
Result
타입을 쓰는 이유는 코드의 간결함을 유지할 수 있어서 쓴다고 생각하는데, do-catch문
을 사용하면 간결성이 떨어지기 때문에 잘 활용하지 않을 거 같습니다!
혹시나 이 부분은 제가 잘못 이해하고 있거나, 더 좋은 활용법이 있으시면 댓글로 알려주세요!😉
extension
Result where Failure == Swift.Error
extension Result where Failure == Swift.Error {
@_transparent
public init(catching body: () throws -> Success) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
}
throw
하는 클로저를 평가하고, 반환된 값을 성공으로 캡처하거나, 발생한 오류를 실패로 캡처하여 새 결과를 생성합니다.
단순 초기화 구문인데 왜 extension
으로 뺐을까요...? 이미 열거형에서 Error
를 채택한 타입만 failure
으로 설정하도록 제약을 두었는데 말이죠..
아시는 분 있으시면 댓글 달아주세요!!
Equatable
extension Result: Equatable where Success: Equatable, Failure: Equatable { }
Hashable
extension Result: Hashable where Success: Hashable, Failure: Hashable { }
Sendable
extension Result: Sendable where Success: Sendable { }
그리고 성공 값일 경우, Equatable
, Hashable
, Sendable
을 채택하고 있습니다!
각각이 어떤 프로토콜인지는 다음 포스트에서 다뤄보겠습니다!
오늘은 가볍게 Result
타입에 대해서 다뤄보았습니다!
Result
타입은 성공과 실패에 대한 반환값을 사용하고 싶은 경우에 사용합니다.
Result
타입이란 Swift5에서부터 Standard library에 추가되었으며,Generic Enumeration
으로 선언되어success
또는failure
두가지case
를 포함하는 타입입니다. 여기서success
는 별도의 제약 조건이 없지만,failure
같은 경우에는Error
를 상속받은 타입이어야만 합니다.