Search

[Swift] Control Flow - Conditional Statements

The Swift Programming Language Documents를 참고해서 정리했습니다. 여러 가지 생각들이 버무러져 있습니다.
contents
*이전 포스팅은 [Swift] Control Flow - Loops 에서 보실 수 있습니다.

Control Flow

Swift는 다양한 Control Flow를 제공합니다.
for-in, while과 같은 반복문, 특정 조건에 따른 분기문, guard, switch, 그리고 실행 흐름 중간에 다른 지점으로 보내주는 break, continue 등을 제공해줍니다.
제공해주는 Control Flow를 사용해서 코드의 흐름을 원하는대로 제어할 수 있습니다.

if

특정 조건을 따라서 코드를 실행하는 게 유용한 경우들이 있습니다. 특정 조건에 부합하면 오류를 발생한다거나, 값이 너무 높거나 낮으면 해당 값이 부적절하다는 메시지를 출력하는 상황에서 말이죠. 이런 상황에 대응하기 위해서 코드를 conditional 하게 작성하게 됩니다.
Swift에서는 조건 분기하는 방법을 2가지 제공합니다.
간단한 조건 평가에 사용하기 좋은 if
복잡한 조건에서 적절한 코드 분기를 통해서 도움을 주는 switch
먼저, 간단한 형태로 단일 조건만 가지면 되는 if부터 알아봅시다.
if단일 조건이 참인 경우에 if문 내부를 실행하게 됩니다.
let score: Int = 40 if score > 50 { print("평균 이상인 학생입니다.") }
Swift
복사
만약, false인 상황에 대해서도 코드를 작성하고 싶다면 else문을 사용해서 작성하면 됩니다.
if score > 50 { print("평균 이상인 학생입니다.") } else { print("평균 이하인 학생입니다.") }
Swift
복사
참일 경우에는 if문 내부로 들어가고, 거짓일 경우에는 else문 내부로 들어가기 때문에 둘 중 하나는 항상 실행하게 됩니다.
if 조건에 추가적으로 조건 코드를 만들고 싶다면 else if를 사용하면 됩니다.
if score > 50 { print("평균 이상인 학생입니다.") } else if score < 0 { print("시험을 보지 않은 학생입니다.") } else { print("평균 이하인 학생입니다.") }
Swift
복사
Swift에서는 if문을 사용해서 변수에 값을 설정할 때에 간단하게 값을 설정할 수 있는 방법을 제공해줍니다.
일반적으로 변수를 만들어두고 if문 내부 코드에서 변수에 값을 설정해주는 식으로 코드를 많이 작성했을 겁니다.
let score: Int = 40 let result: String if score > 50 { result = "평균 이상인 학생입니다." } else if score < 0 { result = "시험을 보지 않은 학생입니다." } else { result = "평균 이하인 학생입니다." } print(result) /* 평균 이하인 학생입니다. */
Swift
복사
하지만, 축약형을 사용하면 간단하게 값을 설정할 수 있습니다. 하지만, Swift 5.9부터 만나볼 수 있는 기능이기 때문에 Xcode 15를 사용하시는 분들만 에러없이 코드를 보실 수 있습니다..
let score: Int = 40 let result = if score > 50 { "평균 이상인 학생입니다." } else if score < 0 { "시험을 보지 않은 학생입니다." } else { "평균 이하인 학생입니다." } print(result)
Swift
복사
if, else if, else문은 동일한 타입의 값을 포함해야 합니다. 만약, 타입이 모두 다르거나 하나가 nil을 반환하게 되면 컴파일 에러가 발생할 수 있습니다.
해당 문제를 해결하기 위해서는 명시적으로 타입을 지정해주어야 합니다.
let result: String? = if score > 50 { "평균 이상인 학생입니다." } else if score < 0 { nil } else { "평균 이하인 학생입니다." }
Swift
복사
nil에다가 직접 명시적 타입을 제공해줘도 됩니다.
let result = if score > 50 { "평균 이상인 학생입니다." } else if score < 0 { nil as String? } else { "평균 이하인 학생입니다." }
Swift
복사

Switch

Switch는 여러 가능한 케이스들과 서로 매칭이 되는지 비교해보고, 성공적으로 매칭되는 케이스의 코드 블록을 실행합니다. 그렇기 때문에, Switch는 여러 상태에 대응이 가능합니다.
Switch문 안에서는 동일한 타입을 가진 하나 이상의 값과 비교가 진행됩니다.
Switch 내부는 여러 케이스로 구성이 되며, 특정 값을 비교하는 단순한 경우도 있지만 더 복잡한 패턴의 케이스와 비교하는 경우도 존재합니다. 이에 대한 자세한 내용은 나중에 설명하겠습니다.
if문에서의 단일 조건처럼 각 케이스도 개별적인 조건을 가지고 있습니다.
Switch에서는 어떤 조건에 부합하는지 비교하고 결정하게 됩니다.
let score: Character = "A" switch score { case "A": print("수") case "B": print("우") case "C": print("미") case "D": print("양") default: print("가") }
Swift
복사
Switch도 if와 동일하게 축약형으로 값 세팅을 진행할 수 있습니다. 하지만, Swift 5.9를 사용하는 Xcode 15부터 에러없이 코드를 볼 수 있습니다.
let score: Character = "A" let koreanScore: String = switch score { case "A": "수" case "B": "우" case "C": "미" case "D": "양" default: "가" }
Swift
복사

Switch의 특징

Swift에서 사용하는 Switch만의 특징이 존재합니다.
⓵ Every Switch Statement must be exhaustive
if문에서는 조건에 대한 모든 상황을 고려하지 않아도 됩니다. 그저 단일 조건을 걸고, 거짓인 경우에 실행되는 else문도 필수적으로 구현하지 않아도 됩니다.
하지만, switch문은 다릅니다. 모든 switch문은 완전해야 합니다.
완전해야 한다는 게 뭔가요?
고려되는 타입의 모든 값이 switch 문에서 제공하는 case 중 하나와 일치해야 한다는 뜻입니다.
하지만, 들어오는 값이 모든 케이스에 부합할 수는 없습니다. 이런 경우에는 default 케이스를 만들어서 모든 케이스에 들어맞지 않는 값에 대한 처리를 진행합니다.
그렇기 때문에, default 케이스는 항상 마지막에 작성되어야 합니다.
⓶ No Implicit Fallthrough
다른 언어를 사용할 때, switch문을 사용하면 원하는 케이스를 지나서 다음 케이스까지도 실행하는 모습을 볼 수 있습니다. 하지만, Swift에서는 현재 케이스를 지나서 다음 케이스로 넘어가지 않습니다.
첫 번째 케이스가 끝나면 즉시 실행을 마칩니다. 이를 통해서 실수로 두 개 이상의 switch문 케이스가 실행되는 것을 방지합니다.
let score: Character = "D" switch score { case "A": print("수") case "B": print("우") case "C": print("미") case "D": print("양") default: print("가") } /* 양 */
Swift
복사
물론, break문을 switch문 안에서 사용할 수 있습니다. 하지만, 다른 언어들과 다르게 해당 케이스만 실행하고 끝내려는 목적으로 사용하는 break가 아닙니다. 해당 케이스 실행 중에 실행을 중단해야 하는 상황에서 사용합니다.
그렇기 때문에, 각 케이스마다 실행 코드를 가지고 있어야 합니다. 만약, 코드가 누락된다면 컴파일 에러가 발생합니다.
하지만, 다른 언어들처럼 현재 케이스를 지나서 다음 케이스까지 실행하고 싶을수도 있습니다.
이런 경우에는 fallthrough를 사용해서 명시적으로 다른 케이스를 실행한다는걸 보여주면 됩니다.
let score: Character = "A" switch score { case "A": print("수") fallthrough case "B": print("우") case "C": print("미") case "D": print("양") default: print("가") } /* 수 우 */
Swift
복사
⓷ Value Binding
Switch에서는 케이스로 들어오는 값에 임시 상수, 변수를 지정할 수 있습니다.
값이 임시 상수로 들어오고 나서 케이스 블록 내부에서 사용할 수 있게 됩니다.
let point = (2, 0) switch point { case (let x, 0): print("x축의 \(x) 위에 있는 좌표") case (0, let y): print("y축의 \(y) 위에 있는 좌표") case let (x, y): print("좌표 어딘가 (\(x), \(y))") } /* x축의 2 위에 있는 좌표 */
Swift
복사

Switch의 다양한 케이스

Switch의 케이스에는 단순한 타입이 설정될 수도 있지만, 그 외에도 다양한 형식으로 케이스가 설정될 수 있습니다.
⓵ Interval Matching
Switch 케이스로 해당 값이 구간에 포함되어 있는지 확인할 수 있습니다.
숫자 구간을 사용해서 해당 구간 내에 값으로 넣은 숫자가 포함되는지 확인하고 해당 값을 케이스 내부 블록으로 보냅니다.
let score = 67 switch score { case 90...100: print("A") case 70..<90: print("B") case 50..<70: print("C") case 30..<50: print("D") default: print("F") } /* C */
Swift
복사
⓶ Tuples
Switch 케이스로 Tuple 형식도 사용할 수 있습니다.
튜플을 사용하게 되면 여러 값들을 사용해서 케이스를 만들 수 있습니다. 서로 다른 값뿐만 아니라 값의 구간을 튜플 내부에 넣을 수도 있습니다. 만약, 해당 위치에 어느 값이든 올 수 있게 하고 싶다면 _를 사용해서 표시해주면 됩니다.
let somePoint = (1, 1) switch somePoint { case (0, 0): print("\(somePoint) 은 원점") case (_, 0): print("\(somePoint) 은 x축 위") case (0, _): print("\(somePoint) 은 y축 위") case (-2...2, -2...2): print("\(somePoint) 는 4 * 4 정사각형 안") default: print("\(somePoint) 는 4 * 4 정사각형 밖") } /* (1, 1) 는 4 * 4 정사각형 안 */
Swift
복사
만약, 위의 코드에 (0, 0)을 넣게 되면 (0, 0)이 switch문의 4개의 케이스에 포함되지만 첫번째 케이스만 실행하고 끝납니다. 위에서 얘기한 다음 케이스로 넘어가지 않는 특징때문에 다른 케이스는 무시하게 됩니다.
⓷ Where
Switch의 각 케이스에 where절을 사용해서 추가 조건을 작성할 수 있습니다.
where절을 사용하게 되면 해당 where절 조건을 만족해야만 해당 케이스에 대한 블록을 실행할 수 있게 됩니다.
let point = (1, -1) switch point { case let (x, y) where x == y: print("(\(x), \(y)) 는 x == y 위에 있음") case let (x, y) where x == -y: print("(\(x), \(y)) 는 x == -y 위에 있음") case let (x, y): print("(\(x), \(y)) 는 좌표 위에 있음") } /* (1, -1) 는 x == -y 위에 있음 */
Swift
복사
⓸ Compound case
케이스끼리 같은 코드를 중복으로 사용하고 있다면, 여러 개의 케이스가 같은 코드를 공유하도록 수정할 수 있습니다. 여러 개의 패턴 중에서 하나라도 일치하면 해당 케이스에 포함된다고 생각해서 케이스 내부 코드를 실행합니다.
let someCharacter: Character = "r" switch someCharacter { case "a", "e", "i", "o", "u": print("\(someCharacter) 는 모음") case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": print("\(someCharacter) 는 자음") default: print("\(someCharacter) 는 모음이나 자음이 아닙니다") } /* r 는 자음 */
Swift
복사
Compound 케이스도 임시 상수, 변수를 만들어서 바인딩할 수 있는데, 대신 조건이 있습니다. 바로, 모든 패턴으로부터 동일한 값을 얻을 수 있어야 합니다.
let point = (9, 0) switch point { case (let distance, 0), (0, let distance): print("축 위에 있습니다. \(distance)정도 원점에서 떨어져 있습니다.") default: print("축 이외의 좌표에 올라가 있습니다.") } /* 축 위에 있습니다. 9정도 원점에서 떨어져 있습니다. */
Swift
복사
위의 예시에서는 distance라는 임시 상수를 두 가지 패턴에서 모두 만들어줬기 때문에, 케이스 내부 코드에서 distance를 사용할 수 있게 됩니다.

defer

Control flow에 포함되는 루프나 조건문은 실행 횟수를 제어하고, 원하는 부분에서만 코드가 실행되도록 제어할 수 있습니다.
defer는 이들과 다르게 코드의 실행을 지연하는 역할을 합니다. delay한다는 뜻이 아니라, 코드 블록을 defer 내부에 작성하게 되면 해당 코드 블록은 현재 실행되는 범위의 끝에 도달하게 됐을 때에 실행되게 됩니다.
defer 코드 블록을 넣게 되면 범위를 빠져나가기 전에 블록을 실행합니다.
벗어나는 방법이 오류로 인한 것인지, 중단문으로 인한 것인지에 관계없이 defer 내부 코드는 범위를 빠져나가면 항상 실행되게 됩니다. 물론, 중간에 런타임 오류나 충돌로 인해서 프로그램 자체의 실행이 중단된다면 defer 블록도 당연히 실행되지 않습니다.
var score = 90 if score >= 0 && score <= 100 { defer { print("점을 얻었습니다.") } print(score, terminator: "") } /* 90점을 얻었습니다. */
Swift
복사
루프에서 사용하게 되면 한 번 루프가 돌 때마다 defer문이 실행되게 됩니다.
var countDown = 10 for i in stride(from: countDown, through: 0, by: -1) { defer { print("!", terminator: " ") } print(i, terminator: "") } /* 10! 9! 8! 7! 6! 5! 4! 3! 2! 1! 0! */
Swift
복사
defer 블록은 범위 내에 2개 이상 사용 시에는 첫 블록이 마지막으로 실행되게 됩니다.
var score = 90 if score >= 0 && score <= 100 { defer { print("축하드립니다.") } defer { print("점을 얻었습니다.") } print(score, terminator: "") } /* 90점을 얻었습니다. 축하드립니다. */
Swift
복사

️ 참고 자료