Swift - 논리 제어문(Conditional Statements)

CPU의 놀고있는 논리회로를 괴롭힐 수 있는게 바로 논리 제어(Conditional Control)다. 이번에는 이 논리(Logical)와 관련된 부분을 살펴보자.

Bool (Boolean, 이진 타입)

우선 제어의 기본은 Bool 타입부터 알아야 한다. Bool 타입은 단순히 true와 false라는 두 가지 논리데이터를 가질 수 있는 비교적 단순한 타입이다.

Bool 타입을 만들 때는 그냥 일반 변수나 상수 만들듯이 넣어도 되지만 논리비교를 통해 값을 생성하기도 한다:
  • A == B: A와 B가 동일하면 true, 아니면 false
  • A != B: A와 B가 다르면 true, 아니면 false
  • A > B: A가 B 보다 크면 true, 아니면 false
  • A >= B: A가 B 보다 크거나 같으면 true, 아니면 false
  • !A: A가 true이면 false, false이면 true (즉 뒤집기)
실제 코드로는 이런 식으로 쓴다.
let tmpBool = true
let tmpBool2 = false
let tmpBool3 = (1 == 1) // true
let tmpBool4 = !true    // false
거의 대부분의 언어에서 통하는 기초적인 이야기다. 아마 대부분 다 알고 있는 상식적인 이야기.

이제 스위프트의 독특한 비교 구문을 보자. ===!== 오퍼레이터다. 자바스크립트에서도 비슷한 모양의 오퍼레이터가 쓰이지만 일반적으로는 거의 쓰이지 않는 모양을 가지고 있다. 아래가 그 예제이다.
var a = 10
var b = 10

a === b       // false
a !== b       // true
오퍼레이터의 특징은 레퍼런스 포인터 비교 연산자라는 점이다. 기존 C나 Objective-C의 경우 일반적으로 인스턴스 변수는 모두 포인터이기 때문에 일반 비교는 전부 포인터간의 비교라 그러려니 하겠지만, 스위프트는 == 오퍼레이터를 여러 용도로 재정의해서 쓸 수 있기 때문에 레퍼런스 비교를 위한 오퍼레이터를 별도로 제공한다.

위의 비교가 성립되는 약간 변형된 예제는 아래와 같다.
var a = 10
var b = a

a === b      // true
이 예제가 성립하려면 스위프트도 한 변수에 다른 변수를 대입(=)하면 데이터가 아닌 레퍼런스 포인터만 넘겨준다 라는 전제가 필요한데, 다행히도 실제 실험 결과 사실이었다. (사실 대부분의 스크립트 언어들도 퍼포먼스 이유로 데이터 대입이 아닌 레퍼런스 대입이 일반화 되어있기 때문에 상식적인 이야기일 수도 있다.)

If blah blah else blah

"만약 이게 뭐뭐하면 이렇게 하고 그게 아니라 이거면 저렇게 하고..." 같은 방식의 논리 흐름을 만드는 코드는 거의 모든 언어가 다 그렇지만 if 라는 키워드가 대표적이다.
let strA = "AAA"
let strB = "BBB"
let strAA = "AAA"

if strA == strAA {
    println("strA is same with strAA")
}

if strA == strB {
    println("strA is same with strB")
} else {
    println("strA is different with strB")
}

if strA != strB {
    println("strA != strB")
}
여기서 문자열 객체를 == 키워드를 이용해서 비교하는데 뭔가 꺼림칙함을 느낀다면 C의 포인터를 이해하고 다룰 줄 아는 분이라는 말이 된다. 다행히도, 스위프트는 오퍼레이터 오버로딩을 지원하기 때문에 문자열에 == 키워드를 이용해 비교하면 포인터가 아닌 데이터를 비교하게 된다. 정말 다행이다. 하하...

따로 설명하지 않아도 될 정도로 쉬운 내용이니 if문의 설명에 대해서는 생략한다.

if 문으로 체크가 가능한 것은 Bool 타입 뿐이다. C나 Objective-C를 하다 스위프트를 처음 접할 때 실수 할 수 있을 만한 코드가 이런 식이다.
let v = 0
if !v {
    v = 10
}
일반 변수는 Bool 타입이 아니다. 그래서 논리 비교가 안된다. 당연히 에러를 내뿜는다. 물론 아래와 같은 식은 된다.
let logic = 10 > 5
if logic {
    println("10 is bigger than 5")
}
logic이라 이름 붙은 상수는 Bool 타입이기 때문에 가능하다.

그렇다면 변수의 인스턴스 생성 유무를 확인 할 때 많이 쓰는 체크 방법은 스위프트에선 못 쓰는건가? 이런 질문이 있다면 옵셔널에 대해 우선 알아야 한다. 정확히 이야기 해서, if 등에서 사용 할 수 있는 논리 타입은 Bool 타입과 함께 Optional 타입이 있다.

[관련글] Swift - 옵셔널(Optionals)

옵셔널을 이용하면 nil 이 false 로 동작하고, non-nil 은 true로 동작하게 되므로 인스턴스 생성 여부 확인이 가능하게 된다.

스위프트의 if문은 특징을 한 가지 가지고 있는데 옵셔널을 연동해서 if문 내부에서만 쓸 지역 상수를 만들어 줄 수도 있다는 점이다.
let someString = "100"
if let v = someString.toInt() {
    println(v) // 100
}
println(v)     // SCOPE ERROR
마지막 라인은 의도적으로 에러가 나는 코드를 집어넣은 것이다. 물론 지역 변수에 대해 아시는 분이라면 그냥 이해하고 계실 것이다.

단순 비교 구문(Ternary Conditional Operator)

C에서 부터 시작된 단순비교형 문법이 스위프트에서 그대로 제공된다.
let va = 10
let vb = 10
var res: Int?

res = (va == vb) ? 100 : 1000
물음표(?) 앞의 논리식이 true 이면 콜론(:) 왼쪽의 식이, false 라면 콜론(:) 오른쪽 식이 res에 대입되는 예제코드다.

Switch - Case

C 혹은 C의 파생 언어에선 왜인지 switch - case 문은 비권장 오퍼레이터였다. 비교 할 수 있는 대상이 수치 뿐이고 break 등을 빼 먹었을 때의 버그 등등 다양한 부분에서 안좋은 영향을 많이 끼쳤나보다.

하지만 스위프트의 switch - case 문은 많이 달라졌다. 아예 괴물이 되어서 돌아왔다. 우선 C와 비슷한 예제를 보자면
let value = 10
switch value {
case 10:
    println("10!")
case 20:
    println("20!")
case 30, 40, 50:
    println("30 or 40 or 50")
default:
    println("WHAT?!")
}
이런 식이다. 일단 거의 비슷하게 보인다. 일치하는 케이스가 없을 때에 실행되는 default 까지도 그대로 들어있다.

그런데 뭔가 다른게 보일 것이다. 바로 break 문이 없다는 점과 case에 콤마를 구분자료 여러 데이터를 집어 넣은 것이다. 이런 식으로 break의 필요성이 사라지게 되었고 그래서 break로 인한 피해가 애초에 발생하지 않도록 새롭게 디자인 되었다.

참고로 break가 필요없어지긴 했지만 의도적으로 break를 사용 할 수도 있다. 스위프트의 case나 default는 반드시 내용을 구현해야 하는데 만약 구현하지 않으면 컴파일 오류가 발생한다. 따라서 특정 케이스의 경우 아무런 동작을 취하지 않게 하고 싶을 때 break를 넣어서 오류를 회피 할 수 있다.
switch ... {
...
default:
    break
}
default가 비는 경우는 얼마든지 있을수 있다. 물론 그 외에 실제 break의 용도(탈출?!)로도 동작이 가능하니 참고해두자.

스위프트에서는 문자열도 case로 사용 할 수 있다.
let someString = "100"
switch someString {
case "100":
    println("100!")
case "200":
    println("200!")
default:
    println("WHAT?!")
}
문자열 비교를 위해 일일이 if 문을 나열해 가며 체크하던 세월이 헛세월이 되어버린 느낌이다.

또 다른 놀라는 점은 Range를 활용한 범위 체크다.
let value = 10
switch value {
case 10:
    println("10!")
case 20:
    println("20!")
case 30...50:
    println("30 ~~ 50")
default:
    println("WHAT?!")
}
앞의 콤마로 구분된 여러 케이스를 그냥 레인지(위 코드에서 30...50)로 합쳐버렸다.

튜플의 경우도 각 엘리먼트를 수월하게 비교 할 수 있다.
let someTuple = (404, "Not Found")
switch someTuple {
case (404, "Not Found"):
    println("HTTP Status 404 Not Found")
case (200, _):
    println("HTTP Status 200 OK")
case (_, "Internal Server Error"):
    println("HTTP Status 500 Internal Server Error")
case (0...199, let message):
    println("I do not know \(message)")
case let (status, message):
    println("Unknown status \(status): \(message)")
}
언더라인은 아무거나 라는 표현으로 이해하자. 그러면 자연스럽게 위 코드 내용이 이해가 될 것이다. 튜플의 모든 엘리먼트가 일치하는 케이스와, 일부가 일치하는 케이스, 그리고 레인지를 이용해 각 엘리먼트의 범위 까지도 케이스에 담을 수 있다. 무시하는 대신 해당 앨리먼트의 내용을 가져 올 수도 있다. 너무나도 유연하다.

그런데 위의 경우 default가 없다. 대신 가장 마지막 케이스는 모든 케이스에 걸리는 조건을 가지고 있다. 이런 식으로 default를 대체해 버리는 방법도 있다는 의미이다. 그리고 이 경우에도 추가 제약을 걸어버리는 방법이 있다.
let someTuple = (404, "Not Found")
switch someTuple {
case (404, "Not Found"):
    println("HTTP Status 404 Not Found")
case (200, _):
    println("HTTP Status 200 OK")
case (_, "Internal Server Error"):
    println("HTTP Status 500 Internal Server Error")
case (0...199, let message):
    println("I do not know \(message)")
case let (status, message) where (status >= 500 && status <= 600):
    println("Unknown status \(status): \(message)")
default:
    println("...")
}
케이스에 where 로 추가 조건을 걸어버리는 초유의 케이스가 탄생한다. 무시무시하다.

스위프트의 switch 문에는 fallthrough라는 플로우 오퍼레이터가 있다. 이것도 스위프트의 독창적인 특징이라고 볼 수 있을 것 같다.
let someTuple = (404, "Not Found")
switch someTuple {
case (404, "Not Found"):
    println("HTTP Status 404 Not Found")
    fallthrough
case (200, _):
    println("HTTP Status 200 OK")
case (_, "Internal Server Error"):
    println("HTTP Status 500 Internal Server Error")
case (0...199, let message):
    println("I do not know \(message)")
case let (status, message) where (status >= 500 && status <= 600):
    println("Unknown status \(status): \(message)")
default:
    println("...")
}
이 코드가 실행되면 콘솔에 아래와 같이 찍힌다.
HTTP Status 404 Not Found
HTTP Status 200 OK
break 문이 없는 스위프트의 switch - case 문이라서 그런지 오히려 break의 반대 기능을 제공한다. 즉 다음 케이스에도 걸리도록 만들고 싶다면 fallthrough 를 명시하면 된다는 말이다. 특수한 플로우를 만들고 싶다면 알아둬도 나쁠 건 없지만 개인적으론 별로 쓰고 싶지 않은 기능이다.

[돌아가기] 스위프트(Swift) 가이드

댓글

이 블로그의 인기 게시물

소수점 제거 함수 삼총사 ceil(), floor(), round()

버전(Version)을 제대로 이해하기