본문 바로가기
YAGOM CAREER STARTER

[토요스터디A반] 20221224: Optional, throws-throw, do-catch, try-try?-try!, Result

by Rhode 2022. 12. 24.

본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.

 


옵셔널 Optional

준비된 옵셔널인 값을 출력하는 여러 방법들을 논의했다. 옵셔널에 대해서는 다른 포스팅에서도 다뤘으니 간단하게 짚고 넘어가겠다.

강제 추출

var walkingItemsList: [String?] = ["하네스", "패딩", "리드줄", "똥봉투", "휴지"]
var lastIndexOfWalkingItemslist = walkingItemsList.count - 1

print("강제추출")
for count in 0...lastIndexOfWalkingItemslist {
    let product = walkingItemsList[count]!
    print("\(count)번 상품은 \(product)입니다.")
}

옵셔널 바인딩 - if let

var walkingItemsList: [String?] = ["하네스", "패딩", "리드줄", "똥봉투", "휴지"]
var lastIndexOfWalkingItemslist = walkingItemsList.count - 1

print("옵셔널 바인딩 - if let")
for count in 0...lastIndexOfWalkingItemslist {
    if let product = walkingItemsList[count] {
        print("\(count)번 상품은 \(product)입니다.")
    }
}

옵셔널 바인딩 - guard let

var walkingItemsList: [String?] = ["하네스", "패딩", "리드줄", "똥봉투", "휴지"]
var lastIndexOfWalkingItemslist = walkingItemsList.count - 1

print("옵셔널 바인딩 - guard let")
for count in 0...lastIndexOfWalkingItemslist {
    guard let product = walkingItemsList[count] else {
        break
    }
    print("\(count)번 상품은 \(product)입니다.")
}

암시적 추출 옵셔널

암시적 추출 옵셔널은 옵셔널을 선언할 때 사용할 수 있는 기능이므로 이미 옵셔널이 선언 되어있는 상황에는 적절하지 않았다.

 

옵셔널 체이닝

연산을 할 대상이 필요해서 해당 상황에는 적절하지 않았다.

 

Nil 병합 연산자

var walkingItemsList: [String?] = ["하네스", "패딩", "리드줄", "똥봉투", "휴지"]
var lastIndexOfWalkingItemslist = walkingItemsList.count - 1

print("Nil 병합 연산자")
for count in 0...lastIndexOfWalkingItemslist {
    let defaultValue = ""
    let product = walkingItemsList[count] ?? defaultValue
    print("\(count)번 상품은 \(product)입니다.")
}

 

 

throws-throw

오류가 나는 조건을 명시하고, 그 오류를 처리해줄 곳으로 전달하는 방법이다. 함수, 메서드, 이니셜라이저의 매개변수 뒤에 throws 키워드를 사용하면 오류를 던질 수 있다. 이런식으로 throws가 표시 된 함수를 throwing function이라고 부른다. 만약 이런 함수에 리턴 타입이 있다면, throws 뒤에 리턴 화살표(->)를 작성해주면 된다. 오류가 발생되었다는 표현은 오류가 발생할 구간에 throw와 오류명을 집어넣으면 된다. 마치 return과 유사하다. 이렇게 던진 오류는 do-try-catch를 통해서 잡을 수 있다.

func buy(productNumber: Int) throws {
    guard productNumber <= lastIndexOfProductList && productNumber>=0 else {
        throw ErrorMessage.outOfRange
    }
    guard let selectedProduct = productsList[productNumber] else {
        throw ErrorMessage.outOfProduct
    }
    guard budget >= price[productNumber] else {
        throw ErrorMessage.outOfMoney
    }
    
    productsList[productNumber] = nil
    budget -= price[productNumber]
    
    print("\(selectedProduct)를 구매하셨습니다.")
    print("예산이 \(price[productNumber])원 차감됩니다.")
    print("남은 잔액은 \(budget)원 입니다.")
    print("")
    
}

 

오류 정의

던질 오류는 열거형(enum)을 통해 그룹화할 수 있다.

enum ErrorMessage: Error {
    case outOfRange
    case outOfProduct
    case outOfMoney
}

오류를 그룹화할 때, raw value가 있다면 그 타입은 프로토콜의 앞에 써주면 된다. 그리고 그 값은 '.rawValue'를 통해 출력할 수 있다.

enum ErrorMessage: String, Error {
    case outOfRange = "상품 번호를 확인하세요."
    case outOfProduct = "해당 상품의 재고가 없습니다."
    case outOfMoney = "예산이 부족합니다."
}

 

 

do-catch

오류를 처리하기 위해서는 do-catch문을 사용할 수 있다. 오류가 do절에 던져지면, catch절을 돌면서 어디에서 오류를 처리할 수 있을지 찾아낸다. do-cath문의 형태는 다음과 같다.

catch 다음에는 어떤 오류가 처리 될 수 있는지 오류명을 적어준다. 만약 catch절에 오류명이 없다면, default 문이 된다. 

func selectItem(productNumber: Int) {
    do {
        try buy(productNumber: productNumber)
    } catch ErrorMessage.outOfRange {
        print(ErrorMessage.outOfRange.rawValue)
    } catch ErrorMessage.outOfProduct {
        print(ErrorMessage.outOfProduct.rawValue)
    } catch ErrorMessage.outOfMoney {
        print(ErrorMessage.outOfMoney.rawValue)
    } catch {
        print("unknown error")
    }
}

 

try-try?-try!

try?

try?를 사용하여 오류를 옵셔널 값으로 바꿔 처리하는 방법이 있다. 예외가 없을 시 옵셔널을 반환하고, 예외가 발생할 시 nil을 반환한다. 그래서 Converting Errors to Optional Values 라고도 부르는 듯 하다. 

if let result = try? doSomething() {
    // doSomething succeeded, and result is unwrapped.
} else {
    // Ouch, doSomething() threw an error.
}

 

try!

try!는 오류가 없는 것이 확실할 때 쓴다. 공식문서에는 Disabling Error Propagation 라고 나와있다. 

 

 

Result

A value that represents either a success or a failure, including an associated value in each case.
각각의 케이스에 대한 연관값을 포함하여 성공과 실패를 모두 나타내는 값이다.

swift5에서 새로이 도입되었다. 애플 문서에는 다음과 같이 정의 되어있다.

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

Result 타입은 열거형(enum)으로 시행된다. 그리고 두 가지의 경우를 가진다: success와 failure. 두 가지 모두 선택에 따른 연관값을 가질 수 있도록 generic(데이터 타입을 미리 정해놓음)을 통해 시행된다. 그리고 failure는 그 타입이 swift의 오류 타입을 따르고 있어야한다. 

 

타입 정의

Result를 사용할 때는, throws-throw혹은 do-catch를 사용할 때처럼 오류를 정의해주어야한다.

success 타입 정의

success는 Void, String, Int 등을 사용해도 된다. 

 

failure 타입 정의

Error를 상속받은 자료형으로 필수적으로 채워줘야한다.

enum ErrorMessage: String, Error {
    case outOfRange = "상품 번호를 확인하세요. \n"
    case outOfProduct = "해당 상품의 재고가 없습니다. \n"
    case outOfMoney = "예산이 부족합니다. \n"
}

 

Result를 이용하여 함수를 만들면 다음과 같은 코드가 나온다.

func buy(productNumber: Int) -> Result<String, ErrorMessage> {
    guard productNumber <= lastIndexOfProductList && productNumber>=0 else {
        return .failure(.outOfRange)
        }
    guard let selectedProduct = productsList[productNumber] else {
        return .failure(.outOfProduct)
        }
    guard budget >= price[productNumber] else {
        return .failure(.outOfMoney)
        }
    return .success(selectedProduct)

}

func order(productNumber: Int) {
    let isOrderable = buy(productNumber: productNumber)
    switch isOrderable {
    case .success(let selectedProduct):
        budget -= price[productNumber]
        print("\(selectedProduct)를 구매하셨습니다.")
        print("예산이 \(price[productNumber])원 차감됩니다.")
        print("남은 잔액은 \(budget)원 입니다.")
        print("")
        productsList[productNumber] = nil
    case .failure(let error):
        print(error.rawValue)
    }
}

func buy(productNumber: Int)의 화살표 뒤에 Result를 적어주고, success로 전달해줄 값, failure로 전달해줄 값을 적어준다. 나의 경우는 success시 selectedProduct라는 String을 전달해줄 것이고 failure시 ErrorMessage를 전달해줄 것이기 때문에 Result<String, ErrorMessage>라고 적어주었다. 

 

그리고 return에 success인지 failure인지 적어준다. 그리고 괄호(()) 안에는 반환할 값이 들어가게 된다. failure인 경우에는 ErrorMessage.outOfRange, ErrorMessage.outOfProduct, ErrorMessage.outOfMoney를 반환해주고, success일 경우에는 selectedProduct를 반환하기로 했다.

 

그리고 그것을 order함수에서 받아주는 것이다. isOrderable이라고 상수를 지정해줘서 buy함수를 호출한다. 그리고 switch문을 사용해서 caserk success와 failure일 경우로 구분해준다. success일 경우에는 budget에서 가격을 빼주고, 위와 같은 문자들을 출력하고 productsList[productNumber]값을 nil로 만들어주는 것이다. 그리고 failure일 경우에는 error의 rawValue를 출력해준다.

failure에서 받아오는 error값이 각각 다르기 때문에, '상품 번호를 확인하세요.', '해당 상품의 재고가 없습니다.', '예산이 부족합니다.' 등 각각 상황에 맞는 오류 메시지를 출력할 수 있다. 이렇게 error상황에 대해서 공통적으로 처리할 수 있다는 장점이 있어 throws-throw&do-catch 보다 코드를 가성비있게 짤 수 있는 것 같다.