2015년 6월 17일 수요일

Swift 2.0 - guard 문

Swift 2.0 에서 컨트롤 제어(Control Flow) 쪽으로 추가된 구문 중 guard 라는게 있다. if 문 같은 것과 비교하면 특별할 건 없지만 코드 읽기 차원에서 유용한 경우를 제공하기 때문에 소개해본다.

참고로 이 글은 Xcode 7 Beta1을 기준으로 쓰여졌고 이후 Xcode 9.2 기준으로 업데이트 하였다.

guard

일단 공식책(?)에서는 Early Exit 라는 이름으로 소개가 되고 있다. 대충 '빨리 탈출한다' 라는 번역은 무시하고 '문제될 것 같으니 미리 빠지자' 라고 돌려 생각하는게 좋다. =_=;;

아래 예제는 특정 함수의 파라미터(인자)가 제대로 들어왔는지 확인해서 모든 파라미터에 값이 넘어오면 뭔가의 작업(?)을 하지만 뭔가 빠진게(nil) 하나라도 있으면 false를 반환하게 구현한 코드이다.
func someFunc(first: Int?, second: String?, third: Bool?) -> Bool {
    guard let firstValue = first, 
          let secondValue = second, 
          let thridValue = thrid 
    else { 
        return false 
    }

    // Some Operation

    if firstValue > 0 {
        print("first is positive number")
    } else {
        print("first is 0 or negative number")
    }

    print("second is \(second)")

    if third {
        print("third is true")
    }

    return true
}
guard 문은 if 문과 비슷해 보이지만 많이 다르다. 특이하게도 else를 항상 달아야 한다. 즉 guard 다음에 오는 구분은 반드시 성공해야 하는 것이고 이 성공해야 할 구문에 문제가 있다면 else 로 묶인 구문이 실행된다는 뜻이다. 마치 assert와 의도가 비슷하다.

즉 이 예제에서 guard 문은 first, second, third 세 가지 파라미터가 모두 값이 있어야만 다음으로 넘어가며, 만약 하나라도 nil이라면 else 블럭이 실행된다. 중도에 빠져버리기 때문에 early exit인 셈이다.

그리고 guard let을 통해 선언된 변수는 else 블럭이 아닌 guard 문 이후부터 옵셔널이 벗겨진 일반 변수로 그대로 활용할 수 있게 된다. Some Operation 이라고 주석을 달아놓은 이후에는 각각 guard let 을 통해 옵셔널이 벗겨진 값들을 액세스 하는 것을 볼 수 있다. guard 문을 사용하면 이런식으로 미리 필요한 옵셔널을 몽땅 벗겨내서 코드가 좀 더 깔끔해지는 효과가 생긴다.

if let 과는 동작이 다르다. if let 구문은 블럭으로 감싸진 내부에서만 옵셔널을 벗겨낸 값을 액세스 하기 위한 용도이니 애초에 용도가 다르다.

위의 guard 문의 경우 옵셔널을 벗겨내기 위한 용도인데 where절을 활용해 조건을 좀 더 상세하게 만들 수도 있다. 콤마(,)를 이용해 조건을 좀 더 복잡하게 만들 수도 있다.
func someFunc(first: Int?, second: String?, third: Bool?) -> Bool {
    guard let firstValue = first, firstValue != 0,
          let secondValue = second, secondValue.count > 0,
          let thirdValue = third, thirdValue
    else { 
        return false 
    }

    ...
    return true
}
이제 first 가 nil이 아니면서 0도 아니고 second 가 nil이 아니면서 빈 문자열이 아니고 third 가 nil이 아니면서 true 일때만 이 guard 문을 통과한다. 이외의 경우라면 몽땅 else 로 분기가 되어서 실행되지 않고 return false 구문이 실행되어 버린다.

예외처리와의 결합(?)

guard 문의 경우 주로 이런 옵셔널 체크 용도로 많이 쓰일거라 생각된다. 더불어 예외(Exceptions) 처리와 결합되는 형태로 많이 쓰이지 않을까 생각된다. 아래와 같은 식으로 말이다.
enum MyError: ErrorType {
    case FirstIsNil
    case SecondIsNil
}

func someFunc(first: String?, second: String?) throws {
    guard let firstValue = first else {
        throw MyError.FirstIsNil
    }
    guard let secondValue = second else {
        throw MyError.SecondIsNil
    }
    
    print("First: \(firstValue), Second: \(secondValue)")
}


do {
    try someFunc(first: "test", second: nil)
}
catch MyError.FirstIsNil {
    print("First is nil")
}
catch {
    print("Another is nil")
}
위 코드는 "Another is nil" 이라는 로그를 콘솔에 남기게 된다. guard의 용도가 이해된다면 그다지 어려울게 없는 코드이다.

[관련글] Swift 2.0 - 예외처리(Error Handling Model)
[관련글] 스위프트(Swift) 가이드

댓글 5개 :

jusung :

항상 잘 보고 있습니다. 좋은 정보 감사합니다.

iOSbeginner :

코드를 실행해봤는데 에러가 있네요. Catch로 잠재적인 에러를 다 잡아주어야 하는 듯 합니다.

Renn Seo :

catch에 별 다른 에러 이름을 적지 않으면 나머지 모든 예외에 걸린다고 보시면 될 것 같습니다.

코딩나그네 :

안녕하세요 스위프트를 처음 배워보는 학생입니다. 제가 잘 몰라서 그러는데 몇가지 질문 드려도 될까요?

1) firstValue = first where first != nil && first != 0 else
에서 왜 first 값이 0이면 안된다는 것인가요? 값이 0이면 0이라는 값이 있는 것 아닌가요?

2) 아랫줄에 secondValue에 대한 코드는 원래 없는 것인가요? 이부분도 설명이 있어야 할 것 같은데...

감사합니다!

Renn Seo :

@코딩나그네

댓글 감사합니다. 제가 봐도 참 어이가 없을 정도로 이상한 코드이고 심지어 오래되기까지 한 터라 아예 원문을 좀 가다듬었습니다. 혹시 다시 봐도 이해가 안되신다면 다시 물어봐주세요.