[Swift] 오류처리

민니·2022년 8월 7일
0

Swift 문법

목록 보기
15/17

swift 언어에서의 오류 처리 방법


오류 처리 구문

swift에서는 오류가 발생하였을 때 함수나 메소드에서 해당 오류를 반환(return)하는 것이 아니라 던진다(throw).
반환하는 것과 던지는 것에 차이점을 생각해 보면, 오류를 반환하려면 함수의 반환 타입과 일치해야 하지만, 오류를 던지는 것은 함수의 반환 타입과 일치하지 않아도 된다.


오류 타입 정의

오류 처리를 위해서는 오류 정보를 담아 함수나 메소드 외부로 던질 오류 타입 객체가 필요하다.


예를 들어 보자!
[YYYY-MM-DD] 형태의 문자열을 분석하여 년, 월, 일 형식의 데이터로 변환하는 함수가 있다고 가정한다. 이 문자열을 분석할 때 생길 수 있는 오류에 대하여 고민해 보자.

  1. 입력된 문자열의 길이가 [YYYY-MM-DD]와 맞지 않는 경우
  2. 입력된 문자열의 형식이 [YYYY-MM-DD]의 형식이 아닐 경우
  3. 입력된 문자열의 값이 날짜와 맞지 않는 경우

오류들은 모두 일관된 주제와 관련된 다양한 경우이다. 따라서, 이를 나타내는 가장 적합한 객체 타입은 바로 열거형이라고 할 수 있다!


protocol Error {

}

오류 타입으로 사용되는 열거형을 정의할 때는 반드시 Error라는 프로토콜을 구현해야 한다. 이 프로토콜을 구현한 열거형이 오류 타입으로 사용해도 된다고 인식되기 때문이다. 구현해야 할 프로퍼티나 메소드는 없다.

그렇다면 이제 오류를 정의해 보자.

enum DateParseError: Error {
    case overSizeString //입력된 데이터의 길이 > 필요한 길이
    case underSizeString //입력된 데이터의 길이 < 필요한 길이
    case incorrectFormat(part: String) //입력된 데이터의 형식 오류
    case incorrectData(part :String) //입력된 데이터의 값 오류
}

✏️ Error 프로토콜을 채택해 준다.



오류 던지기

작성한 오류 타입 객체는 함수나 메소드를 실행하는 과정에서 필요에 따라 외부로 던져서 실행 흐름을 옮겨버릴 수 있다.

➡️ throws 키워드 사용
throws 키워드는 ->(반환 타입 표시 화살표)보다 앞에 작성하여, 오류를 던지면 값이 반환되지 않게 한다.

func canThrowsErrors() throws -> String //오류 객체를 던질 수 있다.
func cannotThrowErrors() -> String //오류 객체를 던질 수 없다.

이렇게 정의된 함수를 호출할 때는 try 키워드를 항상 함수의 이름 바로 앞에 붙인 뒤, 호출해 줘야 한다.

try canThrowsErrors()

이를 이용하여 앞서 언급했던 [YYYY-MM-DD] 형태의 문자열을 분석하여 년, 월, 일 형식의 데이터로 변환하는 함수를 구현해 보자.

import UIKit
import Foundation


enum DateParseError: Error {
    case overSizeString //입력된 데이터의 길이 > 필요한 길이
    case underSizeString //입력된 데이터의 길이 < 필요한 길이
    case incorrectFormat(part: String) //입력된 데이터의 형식 오류
    case incorrectData(part :String) //입력된 데이터의 값 오류
}


struct Date {
    var year: Int
    var month: Int
    var date: Int
}

func parseDate(param: NSString) throws -> Date {
    //입력된 문자열의 길이가 10이 아닐 경우
    guard param.length == 10 else {
        if param.length > 10 {
            throw DateParseError.overSizeString
        } else {
            throw DateParseError.underSizeString
        }
    }
    
    //반환할 객체 타입 선언
    var dateResult = Date(year: 0, month: 0, date: 0)
    
    //연도 정보 분석
    if let year = Int(param.substring(with: NSRange(location: 0, length: 4))) {
        dateResult.year = year
    } else {
        //연도 정보 오류
        throw DateParseError.incorrectFormat(part: "year")
    }
    
    //월 정보 분석
    if let month = Int(param.substring(with: NSRange(location: 5, length: 2))) {
        //월에 대한 값은 1~12만 가능하다.
        guard month > 0 && month < 13 else {
            throw DateParseError.incorrectData(part: "month")
        }
        dateResult.month = month
    } else {
        throw DateParseError.incorrectFormat(part: "month")
    }
    
    //일 정보 분석
    if let date = Int(param.substring(with: NSRange(location: 8, length: 2))) {
        guard date > 0 && date < 32 else {
            throw DateParseError.incorrectData(part: "date")
        }
        dateResult.date = date
    } else {
        throw DateParseError.incorrectFormat(part: "date")
    }
    
    return dateResult
}

try parseDate(param: "2022-08-07")

각각의 오류를 발견할 수 있도록 나누어 구현한다. 이번 포스팅은 오류 처리에 관련된 글이므로, 오류 처리에 집중해 보자.
각각의 경우에서 오류가 날 때는 throw를 이용하여 오류를 던지도록 하였다.
또한 함수를 호출할 때는 앞에 try 키워드를 붙여준다.

하지만, try 키워드는 단순히 함수를 호출할 수만 있을 뿐, 함수에서 던지는 오류를 잡아내지는 못한다.
이를 해결하기 위해 catch 구문을 사용한다.



오류 객체 잡아내기

다른 객체지향언어를 공부하였다면 try-catch는 많이 봤을 것이다. 나도 전에 자바를 할 때 배워본 듯!

catch 구문은 오류가 던져질 경우 이를 잡아내어 적절히 처리해 줄 수 있다.

do {
	try <오류를 던질 수 있는 함수>
} catch <오류 타입1> {
	//오류 타입 1에 대한 처리
} catch <오류 타입2> {
	//오류 타입 2에 대한 처리
} catch <오류 타입3> {
	//오류 타입 3에 대한 처리
} catch ...

예제를 사용하여 오류 객체를 잡아내 보자.

func getParseDate(date: NSString, type: String) {
    do {
        let date = try parseDate(param: date)
    
        switch type {
        case "year":
            print("\(date.year)년입니다.")
        case "month":
                print("\(date.month)월입니다.")
        case "date":
                print("\(date.date)일입니다.")
        default:
            print("입력값에 해당하는 날짜가 없습니다.")
        }
    } catch DateParseError.overSizeString {
        print("입력된 문자열이 너무 깁니다.")
    } catch DateParseError.underSizeString {
        print("입력된 문자열이 너무 짧습니다.")
    } catch DateParseError.incorrectFormat(let part) {
        print("입력값의 \(part)에 해당하는 형식이 잘못되었습니다.")
    } catch DateParseError.incorrectData(part: let part) {
        print("입력값의 \(part)에 해당하는 값이 잘못되었습니다.")
    } catch {
        print("알 수 없는 오류가 발생하였습니다.")
    }
}

오류 타입으로 나누어진 catch 구문은 그에 맞는 오류가 던져졌을 때 잡아내고, 그에 맞는 처리에 따라 오류 정보를 보여준다.

가장 마지막에 오류 타입이 작성되지 않은 catch 구문은 앞의 catch 구문에서 잡히지 않은 모든 오류를 잡아주는 역할을 한다.


getParseDate(date: "2020-12-31", type: "year")
//실행 결과: 2020년입니다.

getParseDate(date: "2020-00-31", type: "year")
//실행 결과: 입력값의 month에 해당하는 값이 잘못되었습니다.

getParseDate(date: "2020-07-aa", type: "year")
//실행 결과: 입력값의 date에 해당하는 형식이 잘못되었습니다.

필요에 의해 오류를 던지지 않게 하고 싶을 때는 try 키워드 대신 try! 키워드를 사용한다.

let date = try! parseDate(param: "2022-00-07")
print("\(date)")
//런타임 에러

이때, 오류가 발생하는 경우 그대로 런타임 오류로 이어진다.


📚
꼼꼼한 재은씨의 Swift:문법편

1개의 댓글

comment-user-thumbnail
2023년 10월 11일

화이팅

답글 달기