제어 흐름 (Control Flow) - 제어 변경 구문 (Control Transfer Statements)

00yhsp·2024년 4월 5일

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

  • continue
  • break
  • fallthrough
  • return
  • throw

Continue

continue 구문은 루프를 통해 다음 반복을 시작하려고 멈추기 위해 사용한다.
이것은 루프를 완전히 벗어나지 않고 "현재 루프 반복은 완료 되었습니다." 라고 알리는 것이다.

아래 예제는 비밀의 퍼즐 구문을 생성하기 위해 소문자 문자열에서 모음과 공백을 삭제한다.

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
    if charactersToRemove.contains(character) {
        continue
    }
    puzzleOutput.append(character)
}
print(puzzleOutput)
// Prints "grtmndsthnklk"

위 코드는 모음 또는 공백이 일치하면 현재 반복의 루프를 종료하고 즉시 다음 반복을 시작하기 위해 continue 키워드를 호출한다.

Break

break 구문은 전체 제어흐름 구문을 즉시 종료한다.
break 구문은 switch 내부나 루프 구문에서 switch 또는 루프 구문을 다른 경우보다 일찍 종료시킬 때 사용될 수 있다.

루프 구문에서 중단 (Break in a Loop Statement)
루프 구문 내에서 사용할 때 break 는 루프의 실행을 즉시 종료하고 제어를 루프의 닫기 중괄호 (}) 다음으로 이전한다.
루프의 현재 반복으로부터 코드는 더 이상 실행되지 않고 루프의 반복은 더 이상 시작되지 않는다.

Switch 구문에서 Break (Break in a Switch Statement)
switch 구문 내에서 사용할 때 break 는 switch 구문을 즉시 종료하고 제어를 switch 구문의 닫힌 중괄호 (}) 다음으로 이동시킨다.

이러한 동작은 일치할 때 사용될 수 있고 switch 구문에서 하나 또는 그 이상의 케이스를 무시할 때 사용된다. Swift의 switch 구문은 완벽하고 빈 케이스를 허락하지 않기 때문에 의도를 명시하기 위해 의도적으로 케이스를 일치시키고 무시해야 하는 경우가 있다.
무시할 케이스의 전체 본문으로 break 구문을 작성하여 이를 수행한다.
해당 케이스가 switch 구문과 일치하면 케이스 내부의 break 구문이 switch 구문의 실행을 즉시 종료한다.

Note:
switch 케이스에 주석만 포함되어 있으면 컴파일 시 에러가 발생한다.
주석은 구문이 아니며 switch 케이스는 무시되지 않는다.
switch 케이스를 무시하려면 break 구문을 필수적으로 사용해야 한다.

아래 예제는 Character 값을 바꾸고 4개의 언어중 하나의 언어로 숫자 기호를 표기하는지 판단한다.
간결함을 위해 단일 케이스에 여러 값이 포함된다.

let numberSymbol: Character = "三"  // Chinese symbol for the number 3
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
    possibleIntegerValue = 1
case "2", "٢", "二", "๒":
    possibleIntegerValue = 2
case "3", "٣", "三", "๓":
    possibleIntegerValue = 3
case "4", "٤", "四", "๔":
    possibleIntegerValue = 4
default:
    break
}
if let integerValue = possibleIntegerValue {
    print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value could not be found for \(numberSymbol).")
}
// Prints "The integer value of 三 is 3."

이 예제는 numberSymbol 이 1 부터 4 의 숫자 기호가 라틴어, 아랍어, 중국어, 태국어 인지 판단한다.
일치하는 것을 찾으면 switch 구문의 케이스 중에 하나는 적절한 정수 값을 possibleIntegerValue 라 불리는 옵셔널 Int? 변수에 할당한다.

switch 구문 이후에 이 예제는 값이 존재하는지 옵셔널 바인딩을 사용한다.
possibleIntegerValue 변수는 옵셔널 타입이기 때문에 초기값은 nil 이므로 possibleIntegerValue 가 switch 구문의 케이스 중 하나와 일치하여 실제 값이 설정된다면 옵셔널 바인딩은 성공할 것이다.

위 예제에서 가능한 모든 Character 값을 리스트화 할 수 없기 때문에 default 케이스로 일치되지 않는 문자를 처리한다.
default 케이스는 어떠한 동작도 수행할 필요가 없으므로 break 구문만 적는다.
default 케이스에 일치되자마자 break 구문은 switch 구문의 실행을 종료하고 이어서 if let 구문을 실행한다.

Fallthrough

Swift에서 switch 구문은 각 케이스의 맨 아래에서 다음 케이스로 넘어가지 않는다.
첫번째 케이스가 일치하자마자 switch 구문의 실행은 완료된다.
반대로 C는 다음 케이스로 넘어가는 것을 막기 위해 모든 switch 케이스 마지막에 명시적으로 break 구문을 명시적으로 넣어야 한다.
기본적으로 다음 케이스로 넘어가지 않는다는 것은 Swift의 switch 구문이 C의 대응문 보다 훨씬 간결하고 예측 가능하다는 것을 의미하므로 실수로 여러 switch 케이스를 실행하지 않는다.

C 처럼 다음 케이스로 넘어가려면 fallthrough 키워드로 케이스 별로 동작을 선택할 수 있다.
아래 예에서 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)
// Prints "The number 5 is a prime number, and also an integer."

이 예제는 description 이라 불리는 새로운 String 변수를 선언하고 초기값을 할당한다.
이 함수는 switch 구문을 사용하여 integerToDescribe 의 값을 고려힌다.
integerToDescribe 의 값이 리스트에서의 소수 중 하나이면 소수라는 것을 나타내는 텍스트를 description 뒤에 추가한다.
그러면 fallthrough 키워드를 사용하여 default 케이스 또한 동작하게 된다.
default 케이스는 추가 설명을 덧붙이고 switch 구문은 완료된다.

integerToDescribe 의 값이 리스트에 없는 소수이면 첫번째 switch 케이스와 전혀 일치하지 않는다.
특정 케이스가 없기 때문에 integerToDescribe 는 default 케이스와 일치된다.

switch 구문 실행이 완료된 후에 숫자의 설명은 print(_:separator:terminator:) 함수를 사용하여 출력된다. 이 예제에서 숫자 5 는 소수로써 유효하다.

Note:
fallthrough 키워드는 switch 케이스 실행을 위한 케이스 조건을 확인하지 않는다.
fallthrough 키워드는 간단하게 C의 표준 switch 구문 동작처럼 다음 케이스 또는 default 케이스로 코드 실행을 직접적으로 이동시킨다.

라벨이 있는 구문 (Labeled Statements)

Swift에서 복잡한 제어흐름 구조를 생성하기 위해 루프와 조건 구문내에 다른 루프와 조건 구문을 중첩할 수 있다.
그러나 루프와 조건 구문은 모두 실행을 조기 종료하기 위해 break 구문을 사용할 수 있다.
따라서 break 구문으로 종료될 루프나 조건 구문에 라벨을 붙이는 것이 유용할 때가 있다.
마찬가지로 여러개의 중첩된 루프를 가지고 있다면 continue 구문에 영향을 받는 루프에 라벨을 붙이는 것이 유용할 수 있다.

이러한 목적을 달성하기 위해 루프 구문 또는 조건 구문에 구문 라벨 (statement label) 을 표기할 수 있다.
조건 구문에서 라벨이 있는 구문에 실행을 종료하기 위해 break 구문과 구문 라벨을 사용할 수 있다.
루프 구문에서 라벨이 있는 구문에 실행을 종료하거나 이어서 진행하기 위해 break 또는 continue 구문과 구문 라벨을 사용할 수 있다.

라벨이 있는 구문 (labeled statement)은 구문의 소개자 키워드와 같은 줄에 위치하고 바로 다음에 콜론이 온다. 여기 예제는 while 루프에 대한 구문의 예이다. 모든 루프와 switch 구문에 대해 동일하다.

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

다음 예제는 이 챕터의 이전에 봤던 Snakes and Ladders 게임에 적절한 버전을 위해 라벨이 있는 while 루프와 break 와 continue 구문을 사용한다. 이번에는 게임에 추가 규칙이 있다.

  • 이기려면 정사각형 25에 정확히 위치해야 한다.
    주사위를 굴려 정사각형 25를 넘어간다면 정확히 정사각형 25에 위치할 때까지 주사위를 굴려야 한다.

    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

이 게임 버전에서는 로직을 위해 while 루프와 switch 구문을 사용한다.
while 루프틑 Snakes and Ladders 게임의 메인 게임 루프를 나타내는 gameLoop라는 구문 라벨을 가지고 있다.

while 루프의 조건은 정사각형 25에 정확하게 착지를 위한 조건을 반영한 while square != finalSquare 이다.

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 구문을 사용한다.

  • 주사위 굴림이 플레이어를 마지막 정사각형으로 이동시킨다면 게임은 종료된다. break gameLoop 구문은 게임 종료를 위해 while 루프의 바깥으로 제어를 이동한다.
  • 주사위 굴림이 플레이어를 마지막 정사각형을 넘어 이동시킨다면 이 이동은 유효하지 않고 플레이어는 주사위를 다시 굴려야 한다. continue gameLoop 구문은 현재 while 루프 반복을 종료하고 루프의 다음 반복을 시작한다.
  • 다른 모든 케이스에 해당하는 주사위 굴림은 유효하다. 플레이어는 diceRoll 만큼 이동하고 게임 로직은 뱀과 사다리에 대해 확인한다. 그러면 루프는 종료되고 다음 턴이 요구되는지 결정하기 위해 while 조건으로 되돌아간다.

Note:
위에서 break 구문을 gameLoop 라벨 없이 사용하면 while 구문을 빠져나오는 것이 아니라 switch 구문을 빠져나온다. gameLoop 라벨을 사용하는 것은 제어 구문을 종료하기 위해 명확하도록 만들어 준다.
루프의 다음 반복으로 이동하기위해 continue gameLoop를 호출할 때 gameLoop 라벨을 사용하는 것은 필요하지 않다. 게임에서 오직 하나의 루프만 존재하므로 루프는 continue 구문의 영향을 받는다. 그러나 continue 구문과 함께 gameLoop 라벨을 사용해도 아무런 문제가 없다. 이렇게 하면 break 구문과 일치하며 게임의 로직을 더 명확하게 읽고 이해하는데 도움을 준다.

profile
iOS Dev

0개의 댓글