본문 바로가기

면접질문정리

[Swift] Result Type이란?

Result Type이란 무엇인가 ?

Result 타입은 열거형 이며 두 개의 success와 failure를 리턴하는 타입입니다. 이때 Success와 Failure 부분의 제네릭타입인데 Failure 타입은 Error를 상속받은 타입이어야 합니다. Result 타입은 에러를 핸들링 하기 위하여 사용하며 실패시에는 미리 선언해둔 error 타입을 넣어주고 성공시에는 성공한 값의 타입을 넣어 주어야 합니다. result의 타입은 열거형이기 때문에 result 값을 처리 하기 위해서는 success와 failure경우를 나누어서 처리를 해줍니다.

@frozen public enum Result<Success, Failure: Error> {
	case success(success)
	case failure(Failure)
}

# 예상되는 꼬리질문

@Frozen에 의미가 무엇인가요 ?

@Frozen의미

Swift Language Guide의 frozen에대한 설명 첫문단에 case유형에 대한 종류 변경을 제한시킵니다. 라고 적혀있습니다. 그래서 향후 라이브러리의 버전 업데이트를 할때 열거형이나 struct의 저장 인스턴스 프로퍼티를 저장 변경 삭제 추가가 불가하게됩니다. @Frozen은 얼어버린다는 의미를 가지고있는데 더 이상의 case를 추가하지않고 오로지 성공과 실패로만 구분하겠다라고 정의한것 같다고 생각됩니다.

그냥 do catch 에러처리로 하면 안되나요? 어떤 차이점이있나요 ?

Result Type을 활용하면 에러 형식을 명시적으로 선언하여 어떤에러를 던지는지 예측할 수 있고 형식 추론을 통해 에러처리코드를 간결히 쓸 수 있습니다. 또한 작업의 실패와 성공을 명확히 구분 할 수 있습니다. 성공 또는 실패를 했을때 값을 가지고 있을 수 있고 원할때 값을 사용하여 처리할 수 도 안할 수 도 있습니다. 이러한 유연성을 가지기때문에 do catch 와 차이점이 있습니다.

enum NumberError: Error {
   case negativeNumber
   case evenNumber
}

func isOddNumber(number: Int) throws -> Int {
   guard number >= 0 else {
      throw NumberError.negativeNumber
   }

   guard !number.isMultiple(of: 2) else {
      throw NumberError.evenNumber
   }

   return number * 2
}

func isOddNumber(number: Int) -> Result<Int, NumberError> {
   guard number >= 0 else {
      return Result.failure(NumberError.negativeNumber)
   }

   guard !number.isMultiple(of: 2) else {
      return .failure(.evenNumber) 
   }

   return .success(oddNumber * 2)
}

# Result Type을 사용하는 일반적인 사례가 어떤게있을까요 ?

일반적으로 비동기 API를 사용할때 많이 볼 수 있습니다.
URLSession을 사용할때 dataTask타입을 보면 Data?, Error? 라는 형태의 클로저를 많이 볼 수 있습니다.
이를 일반적인코드로 구현을 했을때 아래와 같은 패턴으로 구현이 됩니다.

func load(completion: @escaping (Data?, Error?) -> Void) {
    //..
}

load { (data, error) in
    guard error == nil, let error = error else {
			return completion(error) 
		}
    
    guard let data = data else { 
       return completion(error)
    }
    completion(data)
}

이런 방법으로 에러처리를 하게되면 복잡한 상황이 생기는데 실제로 에러처리를 위해 필요한 상태보다 더많은 상태가 존재하기때문에 불필요한 상태까지 처리를 해줘야 합니다.

  1. Data, Error: True, True
  2. Data, Error: True, False
  3. Data, Error: False, True
  4. Data, Error: False, False

위 4가지 경우와 같이 말이죠!

이와 같은 문제를 해결하기 위해 swift 5부터 제안된 해법이 ResultType입니다.

이를 Result Type을 사용하면 명시적이고 간결하게 에러처리를 할 수 있습니다.

enum NetworkError: Error {
	case serverError
	case responseError
	case statuscodeError
	case 404Error
}

// Error타입을 명시해줌으로써 더 정확한 에러표현을 나타내준다.
func load(completion: @escaping (Result<Todo, NetworkError>) -> Void) {
	//..
}

#1
// Result Type을 사용한 에러처리
load { result in
    switch result {
    case .success(let todo):
      completion(.success(todo))
    case .failure(let error):
      completion(.failure(error))
    }
  }

#2
// error를 명시적으로 구분하여 처리해주는 방법
load { result in
    switch result {
    case .success(let todo):
      completion(.success(todo))

    case .failure(let error):
      switch error {
      case .serverError:
        completion(.failure(.serverError))
      case .responseError:
        completion(.failure(.responseError))
      case .statuscodeError:
        completion(.failure(.statuscodeError))
      case .error404:
        completion(.failure(.error404))
      }
    }
  }

# Reference

애플깃헙: https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md

에러처리차이: [https://jusung.github.io/Result-타입/](https://jusung.github.io/Result-%ED%83%80%EC%9E%85/)

forzen공식문서: https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#:~:text=.x