[TIL] 결과 타입

valse·2022년 8월 9일
0

Swift

목록 보기
6/7
post-thumbnail

결과 타입?

Swift 5.0 이후로 등장한 결과 타입은 성공과 에러를 제네릭 파라미터로 받아서 처리할 수 있는 제네릭 열거형이다.
성공한 결과는 Result.success 연관값으로 저장되고, 실패한 결과는 Result.failure 연관값으로 저장된다.
어떤 에러를 어떤 케이스로 나눌 것인지에 대한 에러 타입 자체는 개발자가 do-catch에서 하였듯 정의해주면 되고, 아래처럼 사용한다.

// Failure는 Error 프로토콜을 채택하는 요소, 즉 에러를 의미한다.
// 제네릭 파라미터의 이름은 Jaden Case 표기를 따를 때 커스텀이 가능하다.
@frozen enum Result<Success, Failure> where Failure : Error

결과 타입의 쓸모

결과 타입은 에러 처리에서 굉장히 유용하게 쓸 수 있다.
함수를 정의하는 동시에 어떤 에러를 어떻게 연관값으로 만들 것인지 분기 처리가 가능해진다.
무엇보다 코드 가독성을 매우 크게 높여주고 열거형 케이스를 활용하여 에러 처리도 비교적 접근이 쉬워진다.
실제 프로젝트에서 가져온 예시를 한 번 보자.

enum NetworkError: Error {
	case networkingError
	case dataError
	case parseError
	case requestError
}

final class NetworkManager {
	static let shared = NetworkManager()
	
	typealias NetworkCompletion = (Result<[APIResult], NetworkError>) -> Void
    
	func fetchData(searchTerms: String, completion: @escaping NetworkCompletion) {
		let urlString = "\(SomeApi.requestUrl)\(SomeApi.mediaParam)&term=\(searchTerms)"
		performRequest(with: urlString) { result in
			completion(result)
		}
	}
    
    ... code
}

NetworkManager는 네트워킹을 담당하는 싱글톤 객체이다.
이스케이핑 컴플리션 핸들러를 통해 앞선 메소드의 데이터는 계속 전달될 수 있다.
그런데 이 객체 내에는 NetworkCompletion이라는 함수 타입이 있다.
함수 타입의 파라미터에 명시된 Result<[APIResult], NetworkError>)는 딱 보면 알겠지만, 제네릭 결과 타입이다.
[APIResult]는 네트워킹의 결과를 연관값으로 받아오는 케이스일 것이고, NetworkError 열거형은 Error 프로토콜을 채택하는 에러를 자기 자신의 연관값으로 저장할 것이다.
이를 후행 클로저, 콜백 함수 형태로 사용하고 코드 가독성을 높이기 위해 typealias를 활용한 것이다.

fetchMusic(searchTerms:, completion:)이 실행되면, performRequest(with:,completion:)를 호출하면서 completion에 이 타입의 아규먼트가 전달될 것이다.
정의된 performRequest 메소드는 아래와 같다.

private func performRequest(with urlString: String, completion: @escaping NetworkCompletion) {
		guard let url = URL(string: urlString) else { return }
		let session = URLSession(configuration: .default)
        
		let task = session.dataTask(with: url) { data, response, error in
			guard error == nil else {
				completion(.failure(.networkingError))
				return
			}
			guard let safeData = data else {
				completion(.failure(.dataError))
				return
			}
			
			guard let response = response as? HTTPURLResponse, (200 ..< 299) ~= response.statusCode else {
				completion(.failure(.requestError))
				return
			}
			
			if let successData = self.parseJSON(safeData) {
				completion(.success(successData))
			}
			else {
				completion(.failure(.parseError))
			}
		}
		task.resume()
	}

performRequest(with:completion:)fetchData(searchTerms:, completion:)에서 정의한 completion(result)콜백 함수를 실행할 것이다.
performRequest(with:completion:)내에 정의된 task.resume()가 실행되면서 각 경우에 해당하는 completion 콜백 함수를 실행할 것이다.
이때 파라미터에 전달되는 값이 error라면(Error 프로토콜을 채택한다면), .failure 케이스의 각 연관값으로 처리될 것이다.
반대로 콜백 함수 파라미터로 파싱 데이터가 제대로 전달된다면 .success 케이스에 저장될 것이다.
마지막으로 performRequest의 콜백 함수가 호출될 것인데, 이 콜백 함수는 아래와 같다.

networkManager.fetchMusic(searchTerms: term) { result in
			switch result {
				case .success(let result):
					self.dataArray = result
					DispatchQueue.main.async {
						self.collectionView.reloadData()
					}
				case .failure(let error):
					print(error.localizedDescription)
			}
		}

지금까지 호출한 콜백 함수의 파라미터 result의 타입은 열거형 제네릭 결과 타입이다.
이 콜백 함수를 마지막에 꺼내서 직접 쓰기 위해 각 케이스에 접근하고 있다.
한번 발생한 에러를 각 단계에서 처리하지 않고 열거형을 통해 에러 처리가 가능해졌다.
예시에서는 프린트만 하고 있지만, 더 많은 작업이 가능할 것이다.

다만 콜백함수가 많이 쓰이기 때문에 익숙해지기 전까지는 사용에 어려움이 있을 수 있겠다.

profile
🦶🏻🦉(발새 아님)

0개의 댓글