iOS - Result Type

이한솔·2023년 11월 29일
0

iOS 앱개발 🍏

목록 보기
34/49

기존 에러 처리

기존의 에러처리 방식은 열거형을 선언하고, 해당 열거형의 케이스를 사용하여 에러를 정의한 후, 함수에서 에러를 던져주고 이후에는 do-catch 문을 사용하여 에러를 처리하는 방식이다.

기존 에러 처리 방식

이러한 에러처리의 단점은 열거형을 사용하여 에러를 정의하고, 각 에러 케이스에 대한 처리를 별도의 catch 블록에 작성해야해서 코드의 가독성을 떨어뜨리고 여러 곳에서 같은 에러처리 코드를 반복해서 작성해야 할 수도 있다. 또한, 에러가 추가되거나 수정될 때 변경사항을 반영하기 위해서 코드를 수정해야해서 유지보수를 어렵게 만든다.

Swift에서는 이러한 단점들을 완화하고 개선하기 위한 여러 가지 기능이 추가되고 있다. 그 중 Result Type을 알아보자!



Result Type

Result 타입은 제네릭 열거형으로 선언되어 있고, 경우에 따른 연관값을 포함하여, 성공케이스와 실패케이스를 나타내는 값이다. 성공, 실패의 경우를 깔끔하게 처리할 수 있고 기존의 에러처리 패턴을 완전히 대체하려는 목적이 아니라 개발자에게 에러 처리에 대한 다양한 처리 방법에 대한 옵션을 제공한다.

@frozen enum Result<Success, Failure> where Failure : Error

function의 반환 타입으로 Result Type에 성공했을 경우와, 실패했을 경우의 값을 넣어 넣어준다.


enum CoffeeMachineError: Error {
    case invalidInput
    case insufficienCoffes
    case insufficientFunds(requiredCoins: Int)
}

class CoffeeMachine {
    let coffeePrice: Int = 100
    var coffeeStock: Int = 5
    var depositedCoins: Int = 200
    
    // 동전 투입 메서드
    func insertCoins(coins: Int) -> Result<Bool, CoffeeMachineError> {
        
        // 투입된 동전이 0보다 크지않으면 invalidSelection 에러를 던진다.
        guard coins > 0 else {
            return .failure(.invalidInput)
        }
        
        // 에러가 없을 시 정상 처리한다.
        self.depositedCoins += coins
        print("\(coins)원 동전 투입되었습니다.")
        return .success(true)
    }
    
    // 커피 판매 메서드
    func vendCoffee(numberOfCoffees: Int) -> Result<String, CoffeeMachineError> {
    
        // 구입하는 커피 개수가 커피재고보다 크면 insufficienCoffes에러를 던진다.
        guard numberOfCoffees <= coffeeStock else {
            return .failure(.insufficienCoffes)
        }
        
        // 커피의 총 금액이 디파짓된 코인보다 크면 insufficientFunds 에러를 던진다.
        guard numberOfCoffees * coffeePrice <= depositedCoins else {
            let requiredCoins = numberOfCoffees * coffeePrice - depositedCoins
            return .failure(.insufficientFunds(requiredCoins: requiredCoins))
        }
        
        // 에러가 없을 시 정상 처리한다.
        let totalPrice = numberOfCoffees * coffeePrice
        
        self.depositedCoins -= totalPrice
        self.coffeeStock -= numberOfCoffees
        
        return .success("\(numberOfCoffees)잔의 커피 가격은 \(totalPrice)입니다.")
    }
    
}

switch문을 통해 success, failure의 경우에 따라 처리를 해주면 된다.

let isOrderable = a.insertCoins(coins: 0)
switch isOrderable {
   case .success(let bool):
       print("bool: \(bool)")
   case .failure(let error):
       print("error: \(error)")
   }
        

네트워킹 시 Result Type 사용 예시를 보자.

enum NetworkError: Error {
    case someError
}

func performRequest(with urlString: String, completion: @escaping (Result<Data,NetworkError>) -> Void) {
    
    guard let url = URL(string: urlString) else { return }
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if error != nil {
            // 실패 케이스 전달
            completion(.failure(.someError))  
            return
        }
        
        guard let safeData = data else {
            // 실패 케이스 전달
            completion(.failure(.someError))   
            return
        }
        // 성공 케이스 전달
        completion(.success(safeData))      
        
    }.resume()
}

// switch문을 통해 success, failure의 경우에 따라 처리
performRequest(with: "Url") { result in
    switch result {
    case .failure(let error):
        print(error)
    case .success(let data):
        // 데이터 처리 관련 코드
        break
    }
}

Result 타입을 사용하면 경우에 따라 에러를 명확하게 구분할 수 있어서 코드의 가독성이 높아지고 로직을 명확하게 구현할 수 있다. 또한, 기존의 에러처리 방식에서는 비동기 처리가 복잡할 수 있으나 Result 타입을 사용하면 간편하게 처리할 수 있다.

0개의 댓글