티스토리 뷰

GitHub 레포 하나 소개하고 오늘 글을 시작해보겠습니다. Swift Programming Language Evolution 이라고 Apple에서 운영하는 레포입니다. Swift 언어의 발전을 위해 모두 제안을 할 수 있는 공간이자 다음 Swift 언어가 어떤 방향을 가지고 발전할지 알 들여다 볼 수 있는 곳이기도 합니다. 

 

오늘 dataTask에서 발생할 수 있는 Error의 종류에 대해 다뤄 볼 예정인데 이에 앞서 Result type에 대한 얘기도 해보려구요. SE-0235 에서 Swift 표준 라이브러리에 Result 타입이 처음 소개됩니다. 특히나 비동기 API를 사용한다면 코드가 복잡해지니 간단하고 명확한 방식으로 Error 처리를 할 필요가 있어 Swift 초창기부터 사람들이 Result 타입을 요구했다고 합니다. 그리고는 Swift5에 패치되었다능! (아주 따끈따끈한 친구지요~?)

Result의 구조

public enum Result<Success, Failure: Error> {
   // 'success'는 Success 값을 저장
   case success(Success)
		
   // 'failure'는 Failure 값을 저장
   case failure(Failure)
}

extension Result where Failure == Swift.Error {
   @_transparanet
   public init(catching body: () throws -> Success) {  }
}

extension Result: Equatable where Success: Equatable, Failure: Equatable {  }
extension Result: Hashable where Success: Hashable, Failure: Hashable {  }

 

위에서 보시다시피 success와 failure는 generic으로 구현이되어 있기 때문에 어떤 연관값도 받을 수 있습니다. 다만 failure는 Swift의 Error에 conform 해야하는 조건이 있어요. 그렇기 때문에 Error를 채택해서 custom Error를 만들어낼 수 있어요. NetworkError, AuthenticationError 처럼!

 

URLSession의 dataTask를 통해 데이터를 주고 받는 통신을 하다보면 예기치 못한 네트워크 오류를 마주칠 수 있습니다. 그래서 적절한 error handling이 필요합니다. 때에 따라서는 재요청을 보낼 수도 있어야하고 언제는 사용자에게 재입력을 요구할 수도 있어야 하겠죠. A Error일 때는 B처럼 행동하고 C Error 일 때는 D처럼 행동하는 등 Error가 명확히 정의되어 있으면 error를 다뤄야 하는 개발자도 실수를 줄일 수 있고, 사용자는 불편함 없이 서비스를 사용할 수 있겠죠.

enum NetworkError: Error

URLSession의 dataTask를 활용해 응답을 받아 온 상황을 처리하는 코드는 아래와 비슷하게 작성하시겠죠?

typealias NetworkResult = (Result<Data, NetworkError>) -> ()

func fetchData(completion: @escaping NetworkResult) {
   let request = URLRequest(url: URL(string: "...")!)

   URLSession.shared.dataTask(with: request) { data, response, error in
      ...
      // error handling
      ...
				
      DispatchQueue.main.async { 
         completion(.success(data)) 
      }
   }.resume()
}

 

URLRequest를 URL을 통해 만들 때와 주석으로 error handling으로 작성한 부분에서 몇 가지 상황이 발생합니다. 우선 제가 새롭게 만든 NetworkError 타입을 살펴보시고 각각 상황에 대해 살펴보겠습니다. (가장 기본적인 형태(?)라고 생각하고 충분히 더 세분화 가능합니다.)

enum NetworkError: Error {
    case invalidURL
    case transportError
    case serverError(code: Int)
    case missingData
    case decodingError
}

 

저는 주로 위와 같이 작성해두고 사용합니다. 다음과 같은 상황에 사용합니다.

 

1. invalidURL

String 값으로 URL을 생성하려고 시도한다면(URL(string: "...")) optional 값이 반환 됩니다. 유효하지 않은 URL일 수도 있기 때문에 걸러주기 위함인데요, 이 때 guard문 등을 사용해서 적절한 error handling을 하면 좋을 것 같습니다. 저는 주로 아래와 같이 사용해요.

guard let url = URL(string: "...") else {
	#if DEBUG
	print("\(#function) - Error - invalid url")
	#endif

  completion(.failure(.invalidURL))
}

 

2. transportError

URLSession을 통해 2가지 error를 판단할 수 있는데 그 중 하나가 transport error, 다른 하나가 3번에 있는 server error 입니다. Transport error는 서버에게까지 가다가 문제가 생길 경우 Error? 파라미터에 담아져서 옵니다.

 

3. serverError
Server-side에서 발생하는 error는 Response?에 담겨져 와요. 우리의 요청사항은 전달이 되었지만 서버에서 문제가 생겨서 Response? 파라미터에 담아져서 들어와요. 서버에서 문제가 생겼을 경우 400번대 에러, 정상은 200번대인데 조금 더 세분화 할 수 있는 부분이 이 부분이죠.

 

4. missingData

Data 파라미터도 optional 형태로 오는 이유는 data가 없을 수도 있기 때문입니다. 어떤 종류의 데이터가 넘어와야 하는데 유실되었는 등 data가 없다면 이 역시 적절히 handling을 해줘야 하죠.

 

5. decodingError

Decoding error는 client side에서 개발자가 전달받은 데이터를 가공해서 사용할 수 있도록 파싱을 해야하는데 문제가 생겼는 경우에요. 이 때는 통신에 문제가 생겼다기 보다 개발자가 모델을 잘못 만들었는 등의 실수가 있는 경우라 위와 마찬가지로 저는 이렇게 사용합니다.

case .success(let data):
   guard let recipe = try? JSONDecoder().decode(Recipe.self, from: data) else {
      #if DEBUG
      print("Decoding ERROR : \(#function)")
      #endif
      completion(.failure(.decodingError))
      return
   }
   completion(.success(recipe))

 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함