Swift문법 - (27)Error Handling

Youth·2023년 4월 19일
0

swift문법공부

목록 보기
27/27

아카데미에서 프로젝트를 할때 가장 못했던 부분을 이야기하라면 1초도 고민을 안하고 error handling이라고 이야기할 수 있을만큼 에러처리를 하나도 안해서 문제가 생겼던적이 많다.

  • 에러처리를 안하니 어디서 앱이터지는지를 모른다. 그러다보니 앱이 터질때마다 이런저런 가정들을 세우고 한하나 검증해야했고 에러하나를 해결하는시간보다 찾는데 더오랜시간이 걸렸다
  • 그중에서도 이 문제가 서버쪽에서의 문제인지도 모르고 내 코드문제인줄알고 하루종일 잡고있다가 알고보니 서버쪽 문제여서 시간을 날린적이 참 많다. 이 상황에서의 문제점은 문제를 내 코드에서 찾다보니 내 코드의 문제점만 보이게되고 자존감이 뚝뚝 떨어지게된다...;;;; 사실은 그게아닌데 말이다

그런의미에서 에러처리는 잘해야하고 잘알아야하는 개념이라는걸 ⭐️너무나 많은⭐️삽질을 통해 알게되었다. 사실 어쩌면 우리가 optional을 벗기는 것도 에러처리중에 하나라는 생각이들기는했다. 예전에 정말 아무것도 모르던 시절에는 강제언레핑만 주구장창 썼다... 하지만이제 그러지는 않는걸보니 그때에 비해 성장하긴했다는 생각이 든다.

에러처리를 하는방법은 한가지가 아니다 그렇기때문에 내가 편하거나 아니면 팀에서 정한 방법으로 사용하면된다. 결국 어떤 방식이든 에러처리는 네트워크와의 통신 혹은 앱을 구동하는데있어서 "어떤 문제가 발생할 가능성이 있는 곳에 미리 문제가 생기면 여기를 한번 보러와주세요~"의 느낌이 커서 미리 코드를짤때부터 여기서 이런문제가 발생할수있겠구나를 안다면 앱에 문제가생겼을때 빨리 해결할수있다.

근데 앱출시를 몇개 해보니 느낀건데 약간의 기본 틀은 있는거같다. 네트워크 할때 발생하는 에러는 특정하게 몇개로 정해져있기는한거같다. 그래서 기본적인 error case와 handling만 할줄알면 기본기는 다 잡은거라고 할수있을거같다.

우선 오늘은 Error Handling의 기본 문법에 대해 알아보려한다

에러 처리

  1. 컴파일 타임 에러 : 스위프트 문법과 관련된 에러(컴파일러가 알려줌)
  2. 런타임 에러 : 프로그램이 실행되는 동안 발생 → 앱이터짐
    1. 발생가능한 에러를 미리 처리해 두면, 강제종료되지 않을 수 있음

기본적인 개념

  1. 에러가 발생할수있는 함수라는걸 미리설정해준다(throw 키워드 사용)
    1. input이 들어왔을때 return사이에 throw키워드 사용
  2. 에러가 발생하지 않는경우, 에러가 발생하는 경우를 분기처리해준다(do-catch)
    1. do에는 에러가 발생하지 않았을때 catch는 에러가 발생했을때

에러 처리의 과정 (3단계)

  1. 에러 정의 (어떤 에러가 발생할지 경우를 미리 정의)
//에러 프로토콜 채택 (약속)
enum HeightError: Error {    
    case maxHeight
    case minHeight
}
  1. 함수가 어떤상황일때 error를 return할지를 분기처리 한다
// 에러를 던질수 있는 함수 타입이다
func checkingHeight(height: Int) throws -> Bool {  
    if height > 190 {
        throw HeightError.maxHeight
    } else if height < 130 {
        throw HeightError.minHeight
    } else {
				// 정상적인 경우는 함수의 return형인 true false를 리턴
        if height >= 160 {
            return true
        } else {
            return false
        }
    }
}
  1. 함수의 실행문에 try와 do-catch문으로 처리
// 정상적인 경우의 처리 상황
do {
		// 함수를 실행하면 무조건 try를 붙여야하고(에러발생가능성있는함수라면)
		// 무조건 do문안에 있어야한다
    let isChecked = try checkingHeight(height: 200)
    print("놀이기구 타는 것 가능: \(isChecked)")

// 비정상적인 경우의 처리 상황
} catch { 
		print("놀이기구 타는 것 불가능")
}

에러를 처리하는 방법 - try / try? / try!

  1. 에러 정식 처리 방법
do {
    let isChecked = try checkingHeight(height: 200)
    
    if isChecked {
        print("청룡열차 가능")
    } else {
        print("후룸라이드 가능")
    }
} catch {
    print("놀이기구 타는 것 불가능")
}
  1. try? 옵셔널트라이 → 에러가 발생하면 nil리턴, 정상이라면 리턴타입으로 리턴
    1. 당연히, 옵셔널 타입을 벗겨서 사용해야함
let isChecked = try? checkingHeight(height: 170) 
  1. try! Forced 트라이 → 에러가 날 수 없는 경우에만 사용 가능
let isChecked2: Bool = try! checkingHeight(height: 150)

Catch블럭 처리법

  1. 패턴이 있는 경우(모든 에러를 각각 따로 처리 해야함)
do {
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
} catch HeightError.maxHeight  {    
    print("키가 커서 놀이기구 타는 것 불가능")
} catch HeightError.minHeight {    
    print("키가 작아서 놀이기구 타는 것 불가능")
}
  1. catch자체를 분기처리 하지 않아도 가능
do {
    let isChecked = try checkingHeight(height: 100)
    print("놀이기구 타는 것 가능: \(isChecked)")
    
} catch { 
		// catch문에는 error라는 상수가 제공됨
    // 실제 우리가 정의한 구체적인 에러 타입이 아니라 구체화 해야함
		// 범용적인error에서 구체적인 error로 다운캐스팅해줘야함
    if let error = error as? HeightError { 
        switch error {
        case .maxHeight:
            print("키가 커서 놀이기구 타는 것 불가능")
        case .minHeight:
            print("키가 작아서 놀이기구 타는 것 불가능")
        }
    }
}

rethrowing함수로 에러 다시 던지기(rethrows 키워드)

  • 에러를 던지는 throwing함수를 파라미터로 받는 경우, 내부에서 다시 에러를 던지기 가능
func someFunction1(callback: () throws -> Void) rethrows {
		// catch문 없어도 괜찮음 그런경우엔 do없어도 괜찮음
    try callback()     
}

메서드 / 생성자에 throw키워드의 적용

1.에러 enum(Error프로토콜을채택한)정의
enum NameError: Error {
    case noName
}

class Course {
    var name: String
		2.에러발생할수있는 경우에 throws 키워드 사용    
    init(name: String) throws {
				3.분기처리
        if name == "" {
            throw NameError.noName
        } else {
            self.name = name
            print("수업을 올바르게 생성")
        }
    }   
}

do {
		4.에러발생이 가능한 함수 실행문 앞에 try -> do문에서 실행
    let _ = try Course(name: "스위프트5")
	5.에러가 발생한다면 실행시킬 실행문을 catch문에 정의
} catch NameError.noName {
    print("이름이 없어 수업이 생성 실패하였습니다.")
}

상속관계에서의 throws

(상위) throws (하위) throws재정의 (O 가능)

(상위) 일반 (하위) throws재정의 (O 가능)

(상위) throws (하위) 일반재정의 (X 불가능)

(상위) throws (하위) rethrows재정의 (O 가능)

(상위) rethrows (하위) throws재정의 (X 불가능)

Defer문 - 할일을 미루는 방법

  • defer문은 코드의 실행을 스코프가 종료되는 시점으로 연기시키는 문법
func deferStatement1() {
    defer {
        print("나중에 실행하기")
    }
    print("먼저 실행하기")
}
deferStatement1() //먼저 실행하기 -> 나중에 실행하기

func deferStatement2() {
    if true {
        print("먼저 실행하기")
        return
    }
		// 디퍼문이 호출되어야, 해당 디퍼문의 실행이 예약되는 개념
    defer {                   
        print("나중에 실행하기")
    }

deferStatement2() //먼저 실행하기(나중에실행하기는 실행자체가 안됨)

// 등록한 역순으로 실행
func deferStatement3() {
		// 가장마지막에 실행
    defer { print(1) }
    defer { print(2) }
		// 가장 먼저 실행
    defer { print(3) }
}

deferStatement3() // 321

for i in 1...3 {
    defer { print ("Defer된 숫자?: \(i)") }
    print ("for문의 숫자: \(i)")
}

한사이클마다 defer가 실행되기때문에 123순서가 바뀌지 않는다
for문의 숫자: 1
Defer된 숫자?: 1
for문의 숫자: 2
Defer된 숫자?: 2
for문의 숫자: 3
Defer된 숫자?: 3
profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글