ResultType In Swift
Swift에서 Result Type은 성공 혹은 실패로 끝날 수 있는 작업을 관리하기 좋은 Tool이다.
예를 들어 네트워크 요청을 하는 함수를 실행한다면 실행 결과가 성공시 요청한 데이터를 캡슐화하고 실패시 오류를 캡슐화할 수 있다.
Result Type을 사용하면 예상 경과를 보다 명확하게 정의할 수 있어 가독성과 유지보수성을 향상 시킬 수 있다.
그래서 Swift에서 오류 처리를 개선하기 위해 도입이 되었다고 한다.
Swift에서 Result Type은 성공과 실패 즉, 두가지 경우가 Enum 역할을 한다.
성공은 성공 시 기대되는 값이 되고, 실패는 발생하는 오류 값이 된다.
ResultType Syntax
Swift의 결과 유형은 기본적으로 해당 값으로 성공을 나타내거나 관련 오류가 있는 실패를 나타낼 수 있는 열거형이다. 이 라이브러리는 Swift 표준 라이브러리이다.
그러면 어떻게 사용하는지 확인해보자.
Result Type을 정의하려면 성공 시 가져야할 값과 실패시 포함할 오류 유형을 지정해야 한다. 코드로 확인해보자.
enum DataFetchError: Error {
case networkFailure
case dataCorrupted
case unauthorized
}
// Define a Result type for fetching data
typealias FetchResult = Result<Data, DataFetchError>
위의 코드 예제를 확인해보면 작업이 성공할 때 예상되는 Data와 실패할때 예상되는 DataFetchError가 있다.
성공할대는 함수가 성공적으로 완료될 때 데이터에 접근할 수 있게되며, 실패할때는 어떤 유형의 오류가 발생하였는지 패턴 매칭(switch문)을 통하여 쉽게 구분하고 처리할 수 있다.
성공 및 실패 예제
아래 코드를 보며 이해해보자. 만약 아래 함수가 실패한다면 패턴매칭을 이용하여 failure가 실행될 것 이다.
성공한다면 가져온 데이터를 캡슐화하여 가져올 수 있다.
func fetchData(from urlString: String, completion: @escaping (FetchResult) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(.networkFailure))
return
}
// Simulate fetching data
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
completion(.failure(.dataCorrupted))
return
}
completion(.success(data))
}.resume()
}
// Handling the result of fetchData
fetchData(from: "<https://example.com>") { result in
switch result {
case .success(let data):
print("Successfully fetched \\(data.count) bytes.")
case .failure(let error):
switch error {
case .networkFailure:
print("Network failure - Please check your connection.")
case .dataCorrupted:
print("Data corrupted - Unable to process the data received.")
case .unauthorized:
print("Unauthorized - Access denied.")
}
}
}
장점
Result Type을 사용하여 에러핸들링을 하는 것은 성공 및 실폐 상황을 처리하기 위한 여러 이점이 있다.
- 명확성: 예상되는 성공과 실패 상태를 명확하게 확인할 수 있다.
- 안전성: 성공과 실패를 명시적으로 모두 처리하도록 강요함으로써 처리되지 않는 오류를 줄인다.
- 유연성: 동기화 및 비동기화 작업과 함께 사용할 수 있으며, 다양한 프로그래밍 환경에 다재다능하다.
잠재적인 오류가 있는 작업의 결과를 캡슐화하고 명확하게 정의할 수 있는 능력 덕분에 예측 가능하고 이해하기 쉬운 Swift 코드를 작성할 수 있다.
이 방식은 네트워킹, 데이터베이스 접근, 데이터 처리등 복잡한 개발에서 특히 유용하다.
함수에서 ResultType을 사용할 때
Swift에서 결과 유형은 네트워크 요청, 파일 작업 또는 실패할 수 있는 복잡한 계산과 같이 불확실한 결과를 가진 작업을 수행하는 함수에서 특히 유용한다.
이러한 함수를 정의할 때 성공 시 기대할 수 있는 데이터의 종류와 실패 시 발생할 수 있는 오류의 유형을 명확하게 나타내는 결과 유형을 반환하도록 지정할 수 있다.
예시
아래 코드를 보면 Result Type을 함수에서 어떻게 사용하는 지 확인할 수 있다.
주어진 URL에서 JSON 데이터를 로드하고 Parsing하는 기능을 갖는 함수이다.
어떠한 실패인지에 따라 오류값을 다르게 처리할 수 있다.
enum JSONError: Error {
case invalidURL
case networkError(Error)
case parsingError(Error)
}
func loadJSON(fromURL urlString: String) -> Result<[String: Any], JSONError> {
guard let url = URL(string: urlString) else {
return .failure(.invalidURL)
}
do {
let data = try Data(contentsOf: url)
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
guard let dictionary = jsonObject as? [String: Any] else {
return .failure(.parsingError(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Parsing failed"])))
}
return .success(dictionary)
} catch let error as NSError {
return .failure(.networkError(error))
}
}
아래 코드를 보면 다양한 에러 처리를 손쉽게 철히라 수 있다.
코드의 가독성도 더 좋아지고 유지보수성도 향상된다.
func handleJSONResult(urlString: String) {
let result = loadJSON(fromURL: urlString)
switch result {
case .success(let dictionary):
print("JSON loaded successfully: \\(dictionary)")
case .failure(let error):
switch error {
case .invalidURL:
print("Invalid URL provided.")
case .networkError(let networkError):
print("Network error occurred: \\(networkError.localizedDescription)")
case .parsingError(let parsingError):
print("Error parsing the data: \\(parsingError.localizedDescription)")
}
}
}
// Usage example
handleJSONResult(urlString: "<https://api.example.com/data>")
장점
결과가 있는 함수를 사용하면 불확실한 값을 관리하는데 도움이 되고 모든 결과를 적절히 처리함으로써 안전성있는 애플리케이션을 만들 수 있다.
- 오류처리개선: 성공데이터와 오류를 동일한 유형으로 캡슐화하여 다양한 결과를 처리할 수 있다.
- 향상된 코드 가독성: 패턴매칭을 사용하여 성공 사례와 실패 사례를 명확하게 구분할 수 있다.
- 유연한 제어 흐름: 오류를 처리하는 방법과 시기를 제어할 수 있어 비동기 작업이 많은 프로그래밍에서 유연하다.
ResultType의 고급 기능들
성공한 데이터를 Map을 사용하여 손쉽게 변환하기
Swift의 결과 유형에는 매번 결과를 수동으로 풀지 않고도 변환하고 연산을 연쇄할 수 있는 맵 및 플랫맵과 같은 메서드가 포함되어 있다.
이러한 방법들은 특히 각각 잠재적으로 실패할 수 있는 일련의 작업을 처리할 때 결과를 간소화할 수 있는 것이다.
지도 방법은 오류 유형을 변경하지 않고 결과의 성공 값을 변환하는 데 사용된다.
결과가 성공하면 맵은 주어진 변환 함수를 성공 값에 적용합니다. 결과가 실패하면 변경 없이 오류를 통과한다.
예시
한번 아래 코드를 보면서 이해해보자.
아래 코드들을 보면 map과 flatmap을 이용하여 손쉽게 변환할 수 있다.
func fetchData() -> Result<Data, Error> {
// Simulate fetching data
return .success(Data())
}
let result = fetchData()
let stringResult: Result<String, Error> = result.map { data in
return String(decoding: data, as: UTF8.self)
}
switch stringResult {
case .success(let string):
print("Fetched string: \\(string)")
case .failure(let error):
print("An error occurred: \\(error)")
}
func parseJSON(data: Data) -> Result<[String: Any], Error> {
// Simulate parsing JSON
return .success(["key": "value"])
}
let parsedResult = fetchData().flatMap(parseJSON)
switch parsedResult {
case .success(let dictionary):
print("Parsed dictionary: \\(dictionary)")
case .failure(let error):
print("Failed to parse JSON: \\(error)")
}
ResultType 함수를 여러개 사용하기
Result Type을 사용한다면 각각의 단계에서 실패할 수 있는 시퀀스를 쉽게 처리할 수 있다.
flatMap을 사용하면 각 단계가 이전 단계의 성공적인 결과에 따라 달라지는 작업을 수행할 수 있다.
아래 예제를 보면 flatMap이 이전 단계가 성공했을 때만 각 단계가 진행되는 논리적인 연산 체인이 가능하게 한다.
이전 단계가 실패하면 오류 처리가 됨으로 오류처리도 쉽게할 수 있다.
func downloadData(url: URL) -> Result<data, error=""> {
// Simulate downloading data
return .success(Data())
}
func parseData(data: Data) -> Result<[String: Any], Error> {
// Simulate parsing data
return .success(["data": "parsed"])
}
func processData(url: URL) -> Result<[String: Any], Error> {
downloadData(url: url).flatMap { data in
parseData(data: data)
}
}
// Usage:
let url = URL(string: "<https://api.example.com>")!
let processResult = processData(url: url)
switch processResult {
case .success(let data):
print("Processed data: \\(data)")
case .failure(let error):
</data,>
ResultType에 대한 오류 처리 방식
Swift의 결과 유형은 사용자 지정 오류 유형과 결합하여 던진 오류를 정확하고 유익하게 포착하고 처리할 수 있다.
사용자 정의 오류 유형을 정의하면 일반 오류를 사용하는 것보다 더 구체적으로 오류를 분류할 수 있어 오류 관리 관행의 견고성과 가독성을 향상시킬 수 있다.
예제
아래 네트워크 요청에 대한 오류처리방식을 봐보자.
프로필을 가져오는 동안 발생할 수 있는 다양한 오류들을 나열한다.
다양한 오류들을 효과적으로 처리할 수 있고 성공 및 실패 경로를 단일 유형으로 캡슐화하여 사용하기 때문에 더욱 안전한 구조를 만들 수 있다. 결과를 통해 데이터와 오류의 흐름이 명확해지고 예측이 가능한 오류 처리를 구조화 할 수 있다.
enum NetworkError: Error {
case notConnected
case notFound
case unauthorized
case unexpectedResponse(String)
}
func fetchProfile(userID: String) -> Result<String, NetworkError> {
let profiles = ["001": "John Doe", "002": "Jane Smith"]
guard let profile = profiles[userID] else {
return .failure(.notFound)
}
return .success(profile)
}
let result = fetchProfile(userID: "003")
switch result {
case .success(let profile):
print("Profile found: \\(profile)")
case .failure(let error):
switch error {
case .notConnected:
print("Error: No network connection.")
case .notFound:
print("Error: Profile not found.")
case .unauthorized:
print("Error: User not authorized.")
case .unexpectedResponse(let message):
print("Error: Unexpected response - \\(message)")
}
}
장점
- 명확성: 오류와 성공시 예상되는 데이터를 명확하게 정의할 수 있다.
- 유지보수성: 오류 처리 방식은 유지관리가 더욱 쉽다.
- 디버깅: 오류가 잘 분류되어 디버깅과 로깅이 더욱 간단해진다.
결론
Swift의 Result Type은 성공과 실패를 명확하게 처리할 수 있는 강력한 도구이다.
네트워크 요청, 데이터 처리, 오류 관리 등 다양한 시나리오에서 유용하게 활용할 수 있다.
Result Type을 사용하면 코드의 가독성이 향상되고, 오류 처리가 더욱 체계적이며 예측 가능해진다.
특히 map과 flatMap과 같은 고급 기능을 통해 데이터 변환과 연속적인 작업 처리를 효율적으로 수행할 수 있다.
또한, 사용자 정의 오류 타입과 결합하여 더욱 세밀한 오류 관리가 가능하며, 이는 애플리케이션의 안정성과 유지보수성을 크게 향상시킨다.
Result Type은 Swift에서 비동기 작업과 오류 처리를 더욱 안전하고 효과적으로 다룰 수 있게 해주는 필수적인 도구라고 할 수 있다.
'Swift > 정리' 카테고리의 다른 글
[SwiftUI] TCA(The Composable Architecture)이란? (0) | 2024.03.18 |
---|---|
[SwiftDataStructure&Algorithms] 기본 데이터 구조(세트, 튜플) (0) | 2024.03.15 |
[SwiftDataStructure&Algorithms] 기본 데이터 구조(배열, 딕셔너리) (0) | 2024.03.13 |
[SwiftDataStructure&Algorithms] 데이터 구조 (1) | 2024.03.12 |
XCODE IOS 프로파일링 디버깅 하는 방법 (1) | 2024.01.07 |