본문 바로가기

SWIFT 공식문서 문법 알아보기/05. Control Flow

Swift 공식문서 해설 Control Flow - Control Transfer Statements (5-3)

제어 전달문 (Control Transfer Statements)

제어 전달문은 어떠한 코드에서 다른 코드로 이동함으로써 실행되어야 할 코드의 순서를 바꿉니다. 

 

  • continue
  • break
  • fallthrough
  • return
  • throw

continue, break, fallthrough 구문은 아래에 자세히 다루고 있습니다. return 구문은 Functuions에서 자세히 다루고 throw는 Propagating Errors Using Throwing Functions에서 자세히 다룹니다.

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 문장은 전체 control flow 문장 실행을 즉시 끝나게 합니다. break 문장은 switch, loop문장을 다른 case보다 일찍 끝내려고 할 때 switch, loop 문장 안에 사용됩니다. 

Break in a Loop Statement

loop 문장안에서 사용되어질 때 break는 즉시 loop의 실행 끝으로 가고 loop를 닫는 중괄호(}) 뒤로 코드를 이동시킵니다. 현재 반복한 루프의 코드는 더 이상 실행되지 않고 반복하는 루프도 더 이상 시작하지 않습니다. 

Break in a Switch Statement

switch문장의 안에 사용할 때 break는 switch문장의 실행을 즉시 종료시키고 switch문장을 닫는 중괄호(}) 뒤로 코드를 이동시킵니다. 이와 같은 행동은 switch 문장에서 한 개 혹은 그 이상의 case를 일치시키거나 무시하는 데 사용할 수 있습니다.

스위프트의 switch 문장은 철저하고 비어있는 case를 허용하지 않기 때문에 자신의 의도를 명확하게 하기 위해 가끔은 의도적으로 case를 매치하거나 무시하는  것이 필요합니다. 전체 바디 case에서 원하는 것을 무시하고 싶을 때 break 문장을 사용하면 됩니다. switch 문장에서 case가 매치되었을 때 case안의 break 문장은 switch 문장의 실행은 즉시 끝나게 됩니다.  

NOTE

switch case는 주석만 포함되어도 compile-time 오류가 납니다. 주석은 문장이 아니기 때문에 switch case는 무시하지 못합니다. 항상 switch case를 무시하기 위해서는 break 문장을 사용해야합니다.

아래 예는 Character 값의 변화에 따라 4개의 언어 중 1개의 숫자 기호를 대표하는지 결정합니다. 간결성을 위해 여러 값은 1개의 switch case에 들게 했습니다.

 

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."

 

위의 예는 1~4의 숫자가 라틴, 아라비아, 중국, 태국의 기호인지 결정하기 위해 numberSymbol을 확인합니다. 만약 매치가 발견되었다면 switch 문장의 case들 중의 하나는 적절한 정수 값 possibleIntegerValue라고 불리는 곳에 optional Int? 변숫값을 설정합니다.

switch 문장의 실행이 완료된 후 값을 찾았는지를 확인하기 위해 optional binding을 사용합니다.

변수 possibleIntegerValue는 가상의 optional type이기 때문에 암묵적으로 초기값을 갖고 

possibleIntegerValue가 switch 문장의 처음 4개 중 1개의 case의 의해 실제 값이 설정된다면 optional binding 성공될 것입니다. 

위 예제에서 모든 가능한 Character값을 나열하는 것은 현실적으로 어렵기 때문에 default case는 매치가 되지 않은 어떤 문자도 다룹니다. 이 default case는 어떠한 행동도 취할 필요가 없고 바디에 break 문장만 쓰면 됩니다. default case에 매치되자마자 break 문장은 switch 문장의 실행을 끝내고 if let 문장부터 코드를 계속 실행할 것입니다.

Fallthrough

스위프트에서 switch 문장은 하단의 각각의 case들을 넘어가는 fallthrough를 하지 않습니다. 즉 case가 처음 매칭 하자마자 전체 switch 문장은 실행을 종료합니다. 대조적으로 C언어는 fallthrough를 막기 위해 모든 switch case가 끝나는 곳에 확실히 break 문장을 넣어주는 것을 요구합니다. default fallthrough를 피한다는 것은 스위프트 switch 문장은 C언어에 비해 간결하고 예측 가능하다는 것을 알 수 있으며 그러므로 실수에 의해서 다수의 switch case를 실행하는 것을 피하게 해 줍니다. 만약 C언어 스타일의 fallthrough가 필요하다면 경우에 따라 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이라고 불리는 새로운 문자열 변숫값을 선언하고 초기값을 할당했습니다. 그 후 함수는 switch 문장을 사용해서 integerToDescribe 값을 고려합니다. 만약 integerToDescribe의 값이 리스트의 해당하는 소수(1과 자신의 수 외에는 나눌 수 없는 숫자)중 하나라면 함수는 description 변수 끝에 숫자는 소수라는 텍스트를 덧붙입니다. 그 후 default case에도 속하는지 fallthrough keyword 키워드를 사용합니다. default case는 description 변수 끝에 추가적인 텍스트를 붙이고 switch 문장을 완성합니다.

integerToDescribe의 값이 소수 숫자 리스트에 있지 않는 한 첫 번째 switch case 문장과는 매치되지 않습니다. 다른 구체적인 case가 없다면 integerToDescribe는 default case와 매치됩니다.

switch 문장의 실행이 완료된 후 숫자의 description은 print 함수를 이용하여 나타나 집니다. 위의 예는 숫자 5는 정확하게 소수라고 확인됩니다.

NOTE

fallthrough 키워드는 실행되는 switch case의 case 조건식을 확인하지 않습니다. fallthrough 키워드는 C언어의 switch 문장처럼 단순히 문장 내의 다음 case 블록으로 직접 이동합니다. 

*부연설명

fallthrough키워드는 조건식을 활용하여 특정 case로 넘어갈 수는 없습니다. 단순히 다음 case를 실행시키는 역할만 할 수 있습니다. 

Labeled Statements

스위프트에서 루프와 조건문을 복잡한 control flow 구조를 만들기 위해서 다른 루프와 조건문 안에 넣을 수 있습니다. 그러나 루프와 조건문 둘 다 그들의 실행을 조기에 끝내기 위해 break 문장을 사용할 수 있습니다. 그러므로 break 문장을 사용해 원하는 곳의 루프나 조건식을 종료하기 위해 가끔씩 명확하게 하는 것은 유용합니다. 마찬가지로 만약 다수의 의 루프가 섞여있는 경우 contiune문장이 루프에 영향을 미치는지 명확하게 하는 것이 유용할 수 있습니다. 

이 같은 목적을 달성하기 위해 루프 문장이나 조건식에 statement label을 표시할 수 있습니다. 조건식에서 statement label과 break 문장을 labeled statement의 실행을 끝내기 위해 사용합니다. 루프 문장에서 statement label고 break 혹은 continue 문장을 labeled statement의 실행을 끝내거나 계속할 수 있게 만들어 줍니다. 

labeled statement는 문장의 introducer keyword와 같은 라인에 배치하고 그 뒤에 콜론을 붙입니다. 아래 예는 whille 루프에 대한 구문이지만 모든 루프와 switch 문장은 같은 원리입니다.

 

 

label name: while condition {
statements
}

 

다음 예제는 앞선 장에서 보여주었던 뱀과 사다리 게임을 개조한 버전으로 라벨화 된 while loop와 break와 continue문장 사용합니다. 이번 게임에는 추가된 규칙이 있습니다. 

 

  • To win, you must land exactly on square 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 루프는 gameLoop라고 불리는 문장 라벨을 뱀과 사다리 게임을 위한 메인 게임 나타내기 위해 가지고 있습니다.

while 루프의 조건식은 while square!= finalSquare로 25에 정확히 착륙해야 한다는 것을 반영합니다. 

 

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 문장을 사용합니다. 

 

  • 만약 주사위를 굴렸을 때 마지막 칸(25)으로 간다면 게임은 끝이 납니다. break gameLoop 문장은 while loop 바깥의 첫 번째 라인으로 이동되면 게임은 끝이 납니다. 
  • 만약 주사위를 굴렸을 때 마지막 칸(25)을 넘어서 움직여야 한다면 움직임은 무효가 되고 플레이어는 다시 주사위를 굴려야 합니다. continue gameLoop 문장은 현재의 while loop의 반복을 종료시키고 다음 루프의 반복을 시작합니다. 
  • 이외의 모든 경우 주사위의 수만큼 움직일 수 있습니다. 플레이어는 diceRoll의 숫자만큼 앞으로 가고 뱀과 사다리가 있는지 확인합니다. 루프가 끝난 후 또 다른 반복이 필요한지 결정하기 위해 while 조건식으로 되돌아옵니다.

NOTE

만약 break 문장에 game loop 라벨을 사용하지 않았다면 while 문장이 아니라 switch 문장에서 벗어날 수 있습니다. game loop 라벨을 사용하는 것이 control statement가 종료되어야 하는 부분을 명확하게 할 수 있습니다. 다음 반복 루프를 넘어가기 위해서 continue gameLoop를 부를 때 엄격하게 gameLoop를 사용할 필요는 없습니다. 이 게임에는 하나의 loop 만이 있었고 그러므로 continue 문장이 영향을 미칠 모호함이 없었습니다. 그러나 continue 문장에서 gameLoop의 사용이 어떠한 해로움도 없었습니다. 일관성 있게 break 문장 옆에 라벨을 사용하는 것이 게임의 코드를 명확하게 읽고 이해하는데 도움을 줍니다.