본문 바로가기

개발/Swift

2. 제어문

앞에서 프로그램은 수많은 데이터의 집합으로 이루어져 있다고 설명했다.
하지만 데이터만 모아 놓는다고 해서 프로그램이 되는 것은 아니다.

프로그램의 목적은 주어진 데이터를 가공하여 사용자가 원하는 결과를 내놓는 것인데 이때 데이터를 가공할때 사용하는 것이 제어문이다.
이제 이러한 제어문에 대해 하나씩 정리해본다.

Swift에서는 데이터를 제어하는 방법으로 크게 2가지 방식을 제공하며, 그 방식은 다음과 같다.

  • 조건문
  • 반복문

Swift는 크게 이 2가지 제어문을 써서 데이터를 가공, 처리하여 사용자에게 원하는 결과를 제공한다.

1. 조건문

조건문은 특정 조건에 따라 실행 흐름을 분기시키는 역할을 한다.
Swift에서는 크게 if, switch 이 두개를 통해 조건 분기를 구성한다.

if, else if, else 문

if는 조건 분기의 시작이다. 모두가 알고 있듯이 조건식은 Bool 타입이어야 한다.
여기서는 이런 뻔한 얘기는 다들 알고 있을테니 건너뛰고 Swift만의 특징만 서술하겠다.

if 의 축약형

Swift의 if는 값을 설정할 수 있는 축약형을 제공한다. 중요한건 모든 분기는 동일한 값을 리턴해야하는 것이다.

let grade = if score >= 90 {
    "A"
} else if score >= 80 {
    "B"
} else if score >= 70 {
    "C"
} else {
    "F"
}

print(grade)

// 실행 결과
B
let freezeWarning: String? = if temperatureInCelsius <= 0 {
    "It's below freezing. Watch for ice!"
} else {
    nil
}

let freezeWarning = if temperatureInCelsius <= 0 {
    "It's below freezing. Watch for ice!"
} else {
    nil as String?
}

다만 리턴값에 nil이 들어가는 경우,
swift의 if는 각 분기의 타입을 개별적으로 확인하는데, nil은 어떤 타입에서도 사용 가능하므로 if문이 제대로 된 타입을 추론하는 데 에러를 발생시켜서 어느쪽이든 타입을 명시적으로 작성해줘야 한다.

let weatherAdvice = if temperatureInCelsius > 100 {
    throw TemperatureError.boiling
} else {
    "It's a reasonable temperature."
}

또한 위와 같은 식으로 throw를 사용하여 오류 처리를 할 수 있다.

guard

guard는 Swift에서만 있는 문법으로 조건이 만족되지 않을 경우 즉시 실행 흐름을 종료시키는 제어문이다.
주로 함수 초입에서 전제 조건을 검증하는 용도로 사용된다.

guard를 사용하면 핵심 로직을 중첩 없이 평탄하게 유지할 수 있으며,
조기 종료(Early Exit)를 통한 가독성 향상이 가장 큰 장점이다.

func printUser(name: String?) {
    guard let name = name else {
        print("이름이 없습니다.")
        return
    }

    print("사용자 이름:", name) // guard에서 탈출하면 여기는 실행되지 않는다
}

printUser(name: nil)

// 실행결과
이름이 없습니다.

Switch 문

switch는 "하나의 값을 기준으로 여러 분기 조건을 처리"하는 제어문이다.
Swift의 switch는 다른 언어와 달리 단순한 값 비교를 넘어 패턴 매칭 기반의 강력한 분기 기능을 제공한다.

기본 사용

let number = 2

switch number {
case 1:
    print("하나")
case 2:
    print("둘")
case 3:
    print("셋")
default:
    print("기타")
}

// 실행결과
둘

기존 언어의 switch와 달리 Swift의 switch는 반드시 모든 경우를 처리해야 하며, 누락된 케이스가 있으면 컴파일 에러가 발생한다.
그리고 모든 경우를 처리하지 않을때 사용할 수 있는 default는 switch 구문 제일 밑에 있어야 한다.

그리고 실행결과에서 보면 알다시피, 기존의 언어와 달리 break가 없는데도 아래의 case가 실행되지 않고 바로 switch 구문 전체가 끝난 것을 볼 수 있다.
이렇게 실수로 다른 case가 실행되는 경우를 피하게 하여, 예기치 못한 case의 실행을 막고 의도를 명확히 하여 안전한 코드를 제공하고자 하는 Swift의 철학을 볼 수 있다.

let number = 2

let name = switch number {
case 1:
    "하나"
case 2:
    "둘"
case 3:
    "셋"
default:
    "기타"
}

print(name)

// 실행결과
둘

switch문 또한 if문처럼 값을 설정할 수 있는 축약형을 제공한다. 동일 타입이어야 하는 제약 또한 동일하다.

패턴 매칭

Swift의 switch는 단순한 값 분기를 넘어서, 특정한 패턴에 해당되는 값을 골라내는 방식으로도 사용할 수 있다.
지원하는 패턴매칭은 다음과 같다.

  • 범위일치
    switch 케이스에서 값이 특정 범위에 포함되는지 확인할 때 사용한다.
    let approximateCount = 62
    let countedThings = "moons orbiting Saturn"
    let naturalCount: String
    switch approximateCount {
    case 0:
      naturalCount = "no"
    case 1..<5:
      naturalCount = "a few"
    case 5..<12:
      naturalCount = "several"
    case 12..<100:
      naturalCount = "dozens of"
    case 100..<1000:
      naturalCount = "hundreds of"
    default:
      naturalCount = "many"
    }
    print("There are \(naturalCount) \(countedThings).")
    

// 실행결과
Prints "There are dozens of moons orbiting Saturn.".


- 튜플
switch에서의 튜플은 case별로 구분해서 값이나 값의 범위로 사용할 수 있다.
또한, 와일드카드 패턴(wildcard pattern)으로 알려진 언더바 문자(\_)를 사용하여 가능한 어떠한 값도 일치하게 할 수 있다.
```Swift
let point = (2, 0)

switch point {
case (0, 0):
    print("원점")
case (_, 0):
    print("x축 위")
case (0, _):
    print("y축 위")
default:
    print("기타")
}

// 실행결과
x축 위
  • 값 바인딩

switch 케이스 내에는 일치하는 값을 임시적으로 상수나 변수로 이름을 가질 수 있으며, 케이스 본문 안에서 사용할 수 있다.
값은 케이스의 본문 내부에서 임시적 상수나 변수로 바인드 되기 때문에 이러한 동작을 값 바인딩(value binding)이라 한다.

let anotherPoint = (2, 0) 
switch anotherPoint { 
    case (let x, 0): 
        print("on the x-axis with an x value of \(x)") 
    case (0, let y): 
        print("on the y-axis with a y value of \(y)") 
    case let (x, y): 
        print("somewhere else at (\(x), \(y))") 
} 

// 실행결과
Prints "on the x-axis with an x value of 2".

where 절

Swift에는 where라는 키워드가 추가되었는데, where의 역할은 다양하지만 일단 여기서는 Switch안에서의 역할만 설명한다.

where는 switch 문에서 케이스 조건을 더 정밀하게 제한하기 위한 필터 역할을 한다.
단순히 값이 일치하는지 비교하는 것을 넘어, 추가적인 논리 조건을 붙여서 분기할 수 있도록 해준다.

let number = 7

switch number {
case let x where x % 2 == 0:
    print("짝수")
case let x where x % 2 == 1:
    print("홀수")
default:
    break
}

// 실행결과
홀수

즉, where는

“이 패턴에 해당하면서, 동시에 이 조건도 만족하는 경우에만 실행하라”
라는 의미를 가진다.

보통의 switch에서는 잘 사용하지 않으며, 패턴 매칭을 통해 들어온 분기내에 조건을 걸어주고 싶은 경우로 많이 사용한다.

let age = 25
let isMember = true

switch (age, isMember) {
case let (age, true) where age >= 20:
    print("성인 회원")
case let (age, false) where age >= 20:
    print("성인 비회원")
case let (age, _) where age < 20:
    print("미성년자")
default:
    break
}

// 실행결과
성인 회원

fallthrough

Swift의 switch는 위에서 설명했듯이 기본적으로 자동 break 방식이며,
의도적으로 다음 케이스까지 실행하고 싶을 때만 fallthrough를 사용한다.
다만 fallthrough를 사용하면 다음 case가 실제 실행가능한지 조건을 확인하지 않고 그냥 바로 해당 case의 구문을 실행시킨다는 것을 유의하자.

let number = 1

switch number {
case 1:
    print("1")
    fallthrough
case 2:
    print("2")
default:
    break
}

// 실행결과
1
2

2. 반복문

반복문은 동일한 동작을 여러 번 수행해야 할 때 사용하는 제어문이다.
Swift에서는 for-in, while, repeat-while 루프를 제공한다.

for-in 루프와 while (repeat-while)루프의 차이

여기도 뻔한 얘기는 생략하고, 이 둘의 근본적인 차이에 대해서만 짚고 넘어가겠다.
for-in 루프는 반복 대상과 횟수가 이미 정해진 데이터를 순회하는 반복문이고,
while 루프는 반복 횟수가 아니라 종료 조건이 핵심이 되는 상태 중심의 반복문이다.

추가로, while루프와 repeat-while 루프의 차이는
while 은 루프를 시작할 때마다 조건을 비교하고
repeat-while 은 루프가 끝날때마다 조건을 비교한다는 점이다.
그래서 repeat-while은 일단 첫 루프는 무조건 실행된다는 차이점이 있다.

// for-in
for i in 1...3 {
    print(i)
}

// 실행결과
1
2
3
// while
var count = 3

while count > 0 {
    print(count)
    count -= 1
}

// 실행결과
3
2
1
// repeat-while
var number = 0

repeat {
    print("실행")
    number += 1
} while number < 1

// 실행결과
실행

3. 제어 전송 구문

제어 전송 구문(Control transfer statements)은 한 코드에서 다른 코드로 제어를 변경하여 코드가 실행되는 순서를 변경한다. Swift는 다섯 개의 제어 전송 구문이 있다.

  • continue
  • break
  • fallthrough
  • return
  • throw

이중 return과 throw는 성격이 다르므로 추후 설명하도록 하고, fallthrough는 switch쪽에서만 사용하는 키워드라 위에 미리 설명하였다.

continue

현재 반복을 건너뛰고 다음 반복으로 이동한다.
루프 전체를 종료하는 것이 아닌, 현재 회차를 건너뛰는 것이다.

for i in 1...5 {
    if i == 3 {
        continue
    }
    print(i)
}

// 실행결과
1
2
4
5

break

반복문 또는 switch 문을 즉시 종료하고, 다음 로직으로 넘어간다.
switch에서는 보통 default의 동작을 건너뛰기 위해 사용한다.

for i in 1...5 {
    if i == 3 {
        break
    }
    print(i)
}

// 실행결과
1
2

let number = 3

switch number {
case 1:
    print("1")
case 2:
    print("2")
default:
    break
}

// 실행결과는 없음

4. 그 외

레이블이 있는 구문 (Labeled Statements)

Swift에서는 루프문과 조건문의 중첩이 가능한데, 이 중첩된 상황에서 break를 통한 탈출하고 싶은 제어문을 명확히 하고 싶을때 해당 제어문을 지정할 수 있는 기능을 제공한다.

gameLoop: while square != finalSquare { 
    diceRoll += 1 
    if diceRoll == 7 { diceRoll = 1 } 
    switch square + diceRoll { 
    case finalSquare: 
        // diceRoll will move us to the final square, so the game is over 
        break gameLoop 
    case let newSquare where newSquare > finalSquare: 
        // diceRoll will move us beyond the final square, so roll again 
        continue gameLoop 
    default: 
        // this is a valid move, so find out its effect 
        square += diceRoll 
        square += board[square] 
    } 
 } 
 print("Game over!")

switch 내에서 finalSquare case가 실행될때 switch를 탈출하는 것이 아닌 gameLoop를 지정하여 전체 while루프를 탈출하라고 지정한 것을 확인할 수 있다.

defer

현재 실행중인 코드블럭이 종료될 때 마지막으로 실행될 로직을 작성할 때 사용한다.
defer로 작성된 로직은 프로그램이 크래쉬나 런타임 오류 등으로 실행이 중지되는 경우 외에는 무조건 실행된다.

var score = 1 
if score < 10 { 
    defer { 
        print(score) 
    } 
    score += 5 
} 

// 실행결과
6

아래의 코드처럼 한 블럭내에 defer를 여러 개가 있는 경우의 실행순서는 역순이 된다.

if score < 10 { 
    defer { 
        print(score) 
    } 
    defer { 
        print("The score is:") 
    }
    score += 5 
} 

// 실행결과
The score is:
6

'개발 > Swift' 카테고리의 다른 글

5. 컬렉션  (0) 2026.01.28
4. 클로저  (0) 2026.01.23
3. 함수  (0) 2026.01.23
1. 기본 문법  (0) 2026.01.09
0. Prologue  (0) 2026.01.07