🔥 제어 흐름 변경하기

1166자
14분

Swift에서는 continue, break, fallthrough, return, throw와 같은 5가지 제어 흐름 전환문(control transfer statements)이 있습니다. 이 키워드들은 코드의 실행 흐름을 변경해서 한 코드에서 다른 코드로 제어를 이동시킬 때 사용됩니다.

continue

continue는 루프 안에서 현재 iteration을 끝내고 다음 iteration으로 넘어가게 해주는 키워드입니다.

예를 들어, 아래 코드는 문자열에서 모음과 공백을 제거하고 퍼즐 문구를 만들어냅니다:

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
 
for character in puzzleInput {
    // 제거할 문자가 포함되어 있으면
    if charactersToRemove.contains(character) {
        // 현재 iteration을 건너뛰고 계속 진행합니다
        continue
    }
    // 제거할 문자가 아니면 출력 문자열에 추가합니다
    puzzleOutput.append(character)
}
 
print(puzzleOutput) // "grtmndsthnklk"
swift

루프 내에서 모음이나 공백을 만나면 continue가 호출되어 해당 iteration이 즉시 종료되고 다음 iteration으로 건너뜁니다.

break

break는 control flow statement의 실행을 즉시 종료하는 키워드입니다. switch나 루프 문에서 실행을 조기에 종료하고 싶을 때 사용할 수 있습니다.

루프문에서의 break

루프문 내에서 break를 사용하면 루프의 실행이 즉시 종료되고 } 다음의 코드로 제어가 이동합니다. 현재 iteration의 나머지 코드는 실행되지 않고, 이후의 iteration도 시작되지 않습니다.

Switch문에서의 break

switch문 내에서 break를 사용하면 switch문의 실행이 즉시 종료되고 } 다음의 코드로 제어가 이동합니다.

이 기능은 switch문에서 하나 이상의 case를 매치하고 무시하는 데 사용할 수 있습니다. Swift의 switch문은 빈 case를 허용하지 않기 때문에, 의도를 명시적으로 표현하기 위해 무시하려는 case에 break를 작성해야 할 때가 있습니다. 무시할 case의 본문 전체에 break를 작성하면 됩니다.

아래는 Character 값을 switch하여 4개 언어 중 하나의 숫자 기호를 나타내는지 확인하는 예제입니다:

let numberSymbol: Character = ""  // 숫자 3의 중국어 기호
var possibleIntegerValue: Int?
 
switch numberSymbol {
case "1", "١", "", "":
    possibleIntegerValue = 1
case "2", "٢", "", "":
    possibleIntegerValue = 2
case "3", "٣", "", "":
    possibleIntegerValue = 3
case "4", "٤", "", "":
    possibleIntegerValue = 4
default:
    // 매치되는 case가 없으면 그냥 무시합니다
    break
}
 
if let integerValue = possibleIntegerValue {
    print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value couldn't be found for \(numberSymbol).")
}
// "The integer value of 三 is 3."를 출력합니다.
swift

switch문에서 숫자 1부터 4까지의 라틴어, 아랍어, 중국어, 태국어 기호에 대해 확인합니다. 매치되는 case가 있으면 possibleIntegerValue라는 Int? 변수에 해당 정수값을 설정합니다.

switch문 실행이 끝나면 optional binding을 사용해서 값이 설정되었는지 확인합니다. possibleIntegerValue는 optional 타입이므로 초기값이 nil이고, optional binding은 switch문의 첫 4개 case 중 하나에 의해 실제 값이 설정된 경우에만 성공합니다.

모든 가능한 Character 값을 나열하는 것은 현실적이지 않으므로, default case에서 매치되지 않는 문자를 처리합니다. 이 default case는 아무런 동작도 수행할 필요가 없으므로 break문 하나로 본문을 작성했습니다. default case가 매치되면 break문에 의해 switch문이 즉시 종료되고, if let 문 다음부터 코드 실행이 계속됩니다.

fallthrough

Swift에서 switch문은 첫 번째로 매치된 case가 완료되는 즉시 전체 실행이 완료됩니다. 이는 각 case의 끝에서 다음 case로 fall through되지 않는다는 뜻입니다. 반면 C에서는 fallthrough를 막기 위해 모든 switch case의 끝에 명시적인 break문을 삽입해야 합니다. 기본적인 fallthrough 동작을 피함으로써 Swift의 switch문은 C에 비해 훨씬 간결하고 예측 가능하며, 실수로 여러 switch case를 실행하는 일이 없습니다.

C 스타일의 fallthrough 동작이 필요하다면 case별로 fallthrough 키워드를 사용해서 옵트인(opt-in)할 수 있습니다. 아래 예제는 fallthrough를 사용해서 숫자에 대한 텍스트 설명을 만듭니다:

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
 
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}
 
print(description)
// "The number 5 is a prime number, and also an integer."를 출력합니다.
swift

이 예제에서는 description이라는 새 String 변수를 선언하고 초기값을 할당합니다. 그런 다음 switch문을 사용해서 integerToDescribe의 값을 검토합니다. integerToDescribe의 값이 소수 목록에 있으면 숫자가 소수라는 텍스트를 description 끝에 추가합니다. 그런 다음 fallthrough 키워드를 사용해서 default case로 "떨어집니다". default case는 추가 텍스트를 description 끝에 덧붙이고 switch문이 완료됩니다.

integerToDescribe의 값이 알려진 소수 목록에 없으면 첫 번째 switch case에 전혀 매치되지 않습니다. 다른 특정 case가 없으므로 integerToDescribedefault case에 매치됩니다.

switch문 실행이 끝나면 print(_:separator:terminator:) 함수를 사용해서 숫자에 대한 설명을 출력합니다. 이 예제에서는 숫자 5가 소수로 올바르게 식별되었습니다.

레이블이 붙은 문

Swift에서는 루프와 조건문을 다른 루프와 조건문 안에 중첩해서 복잡한 제어 흐름 구조를 만들 수 있습니다. 그러나 루프와 조건문 모두 break문을 사용해서 실행을 조기에 종료할 수 있습니다. 따라서 때로는 break문으로 어떤 루프나 조건문을 종료할지 명시적으로 표현하는 것이 유용합니다. 마찬가지로 여러 개의 중첩된 루프가 있다면 continue문으로 어떤 루프에 영향을 줄지 명시적으로 표현하는 것도 유용할 수 있습니다.

이를 위해 statement label을 사용해서 루프문이나 조건문을 표시할 수 있습니다. 조건문에서는 break 문과 함께 statement label을 사용해서 레이블된 문의 실행을 종료할 수 있습니다. 루프문에서는 breakcontinue 문과 함께 statement label을 사용해서 레이블된 문의 실행을 종료하거나 계속할 수 있습니다.

labeled statement는 statement 도입부 키워드와 같은 줄에 레이블을 배치하고 콜론을 붙여서 표시합니다. 다음은 while 루프에 대한 구문 예제이지만, 모든 루프와 switch문에 대해서도 동일한 원리가 적용됩니다:

<#label name#>: while <#condition#> {
   <#statements#>
}
text

다음 예제는 이 장 앞부분에서 본 Snakes and Ladders 게임의 수정 버전에 대해 레이블이 붙은 while 루프와 함께 breakcontinue문을 사용합니다. 이번에는 게임에 추가 규칙이 있습니다:

  • 이기려면 정확히 25번 칸에 도착해야 합니다.

특정 주사위 굴림이 25번 칸을 넘어서게 한다면 정확히 25번 칸에 도착하는 데 필요한 숫자가 나올 때까지 다시 굴려야 합니다.

lecture image

finalSquare, board, square, diceRoll의 값은 이전과 동일한 방식으로 초기화됩니다.

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
 
swift

이 버전의 게임은 while 루프와 switch문을 사용해서 게임 로직을 구현합니다. while 루프에는 game루프라는 statement label이 붙어 있어서 Snakes and Ladders 게임의 메인 게임 루프임을 나타냅니다.

while 루프의 조건은 while square != finalSquare로, 정확히 25번 칸에 도착해야 한다는 점을 반영합니다.

game루프: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
 
    switch square + diceRoll {
    case finalSquare:
        // 주사위 굴림으로 마지막 칸에 도착하므로 게임이 끝납니다
        break game루프
    case let newSquare where newSquare > finalSquare:
        // 주사위 굴림으로 마지막 칸을 넘어가므로 다시 굴립니다
        continue game루프
    default:
        // 유효한 이동이므로 효과를 확인합니다
        square += diceRoll
        square += board[square]
    }
}
 
print("Game over!")
swift

주사위는 각 루프의 시작 부분에서 굴려집니다. 플레이어를 즉시 이동시키는 대신 루프는 switch문을 사용해서 이동 결과를 고려하고 이동이 허용되는지 판단합니다:

  • 주사위 굴림으로 플레이어가 마지막 칸으로 이동하면 게임이 끝납니다. break game루프문은 제어를 while 루프 바깥의 첫 번째 코드 줄로 이동시켜 게임을 끝냅니다.
  • 주사위 굴림으로 플레이어가 마지막 칸을 넘어가면 이동이 유효하지 않고 플레이어는 다시 굴려야 합니다. continue game루프문은 현재 while 루프의 iteration을 끝내고 루프의 다음 iteration을 시작합니다.
  • 다른 모든 경우에는 주사위 굴림이 유효한 이동입니다. 플레이어는 diceRoll 칸만큼 앞으로 이동하고, 게임 로직은 뱀과 사다리가 있는지 확인합니다. 그런 다음 루프가 끝나고 제어는 while 조건으로 돌아가서 다른 턴이 필요한지 결정합니다.

switch문이 실행을 마치면 print(_:separator:terminator:) 함수를 사용해서 "Game over!"를 출력하고 게임이 끝납니다.

정리해 보면:

  • continue는 루프의 현재 iteration을 건너뛰고 다음 iteration으로 넘어가게 해줍니다.
  • breakswitch나 루프문의 실행을 즉시 종료시킵니다.
    • 루프문에서는 현재 iteration의 나머지 코드를 실행하지 않고 루프 바깥의 코드로 제어를 이동시킵니다.
    • switch문에서는 switch문 실행을 즉시 종료하고 switch 바깥의 코드로 제어를 이동시킵니다. 이를 이용해 특정 case를 무시할 수 있습니다.
  • fallthrough는 C 스타일의 case 사이 fall through 동작을 구현할 때 사용합니다.
  • statement label을 사용하면 중첩된 루프나 조건문에서 breakcontinue로 어떤 statement를 제어할지 명시할 수 있습니다.