본문 바로가기

Swift/Swift Language

Swift 언어 가이드 - 흐름 제어

흐름제어 Control Flow

스위프트는 다양한 흐름 제어 문(statement)를 제공합니다. 이 중에는 작업을 여러번 수행할 수 있는 while 반복문이나, 조건에 따라 다른 코드의 흐름을 실행하기 위한 if, guard, swift문이 있습니다. 그리고 break, continue 처럼 문은 코드의 다른 곳으로 실행의 흐름을 이동하기 위한 것도 있습니다.

스위프트는 for-in 반복문을 지원합니다. 이 반복문은 배열, 딕셔너리, 범위, 문자열, 다른 시퀀스 등을 쉽게 순회할 수 있도록 합니다.

스위프트의 switch문은 C 언어의 switch문 보다 강력합니다. 스위프트의 switch느ㄴ 여러 조건들을 다른 패턴들과 일치할 수 있습니다. 예를 들어, 범위, 튜플, 특정 타입으로 캐스팅 등이 있습니다. switch에서 조건에 맞는 값은 임시 상수나 변수에 저장하여 조건문 내부에서 사용할 수 있으며, 복잡한 조건문을 각각의 케이스에 where절과 함께 표현할 수 있습니다.

For-In 반복문

여러분은 for-in반복문을 사용하여 시퀀스를 순회할 수 있습니다. 시퀀스에는 배열, 숫자의 범위 혹은 문자열안의 문자가 있습니다.

배열의 아이템을 순회하기 위한 For-in 반복문은 다음과 같이 작성할 수 있습니다.

let names = ["Anna", "Alex", "Biran", "Jack"]
for name in names {
  print("Hello, (name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!

또한, 여러분은 딕셔너리의 키-값 쌍에 접근하기 위해 딕셔너리를 순회할 수 있습니다. 딕셔너리를 순회할 때, 딕셔너리에서 각각의 아이템은 (key, value) 튜플로 반환됩니다. 그리고 for-in 반복문의 몸체안에서 키-값 쌍을 사용하려면 (key, value) 튜플의 멤버들을 명시적인 이름의 상수로 분해해야 합니다. 아래의 예제에서, 딕셔너리의 키가 animalName이라는 이름의 상수로 분해되며 딕셔너리의 값은 legCount의 이름으로 분해됩니다.

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for(animalName, legCount) in NumberOfLegs {
  print("\(animalName)은 다리가 \(legCount)개 이다.")
}
// cats은 다리가 4개 이다.
// ant은 다리가 6개 이다.
// spider는 다리가 8개 이다.

Dictionary의 아이템은 순서가 정해져 있지 않으며, 아이템을 순회할 때 그것들이 추출되는 순서는 지켜지지 않습니다. 여러분이 Dictionary에 아이템을 추가하는 것은 그것이 순회될 때의 순서를 뜻하지 않습니다. 배열과 딕셔너리에 대한 더 자세한 사항은 컬렉션 타입(Collection Types)을 참고하세요,

for-in 반복문은 숫자 범위와 함께 사용될 수 있습니다. 아래의 예제는 구구단 5단의 처음 몇개의 숫자들입니다.

for index in 1...5 {
  print("\(index) 곱하기 5 는 \(index * 5)")
}
// 1 곱하기 5는 5
// 2 곱하기 5는 10
// 3 곱하기 5는 15
// 4 곱하기 5는 20
// 5 곱하기 5는 25

5를 포함하여, 1부터 5까지 숫자의 범위가 연속적으로 반복됩니다. (...)로 표현되는 닫힌 범위 연산자의 사용을 볼 수 있습니다. index의 값은 범위 1의 첫 번째 숫자로 저장됩니다. 그리고 반복문 내부의 코드가 실행됩니다. 이 반복문의 경우, 오직 하나의 명령문인 print(_:)만을 실행합니다. 해당 명령문이 실행되고 나서, 범위 2의 두 번째 값을 가지기 위해 index의 값은 수정됩니다. 그리고 print(_:separator:terminator:) 함수가 다시 호출됩니다. 이런 동작이 범d위의 끝에 도달할 때 까지 계속됩니다.

위 예제에서, index는 상수입니다. index의 값은 반복문을 매번 돌 때마다 자동으로 알맞은 값이 저장됩니다. 또한 이 변수는 사용되기 전에 선언될 필요가 없으므로 반복문이 선언된 곳에서 함께 암시적으로 선언됩니다. 따라서, let 선언 키워드가 필요없습니다.

만약 어떠한 시퀀스로부터 각각의 값들이 필요가 없다면 언더스코어(_)를 변수의 이름에 두면 됩니다.

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
  answer *= base
}
print("\(base)의 \(power)제곱은 \(answer)이다. ")
// "3의 10제곱은 59049이다"

이 예제는 어떠한 숫자의 값에 다른 숫자의 값을 제곱합니다.( 예제의 경우 3의 10제곱) 이 값은 1부터 시작하여(즉 3의 0제곱), 3을 열번 곱합니다. _닫힌 범위연산자_를 사용하여 1부터 10을 포함하여 반복문이 종료됩니다. 이러한 계산식에서는 각각의 카운터 값은 반복문에서는 쓸모가 없습니다. 이 코드는 단순히 적절한 횟수만큼을 반복합니다. 언더스코어 문자(_)는 반복문의 변수에 사용되어 무시됩니다. 또한 반복문을 순회하는 동안 그 값에 접근하지 않습니다.

몇몇의 경우에는, 범위의 양 끝이 포함된다면 닫힌 범위를 사용할 필요가 없습니다. 시계의 모든 분을 표시하려고 한다고 가정해봅시다. 0부터 60 까지 바늘이 향할 곳을 그려야 합니다. 마지막 범위는 포함하지 않으면서 처음의 값을 포함하고 싶을 때, 절반이 닫힌 범위 연산자(..<)를 사용하세요. 범위에 대해서는 범위 연산자(Range Operators)를 참고하세요.

let minutes = 60
for tickMark in 0..<minutes {
  //0부터 59까지 표현
}

어떤 사용자는 UI상에서 더 적은 분(minute) 표시를 보고 싶을 수도 있습니다. 예를 들어, 시계의 매 5분마다 어떤 표시를 하고 싶다면, stride(from:to:by:) 함수를 사용하여 원하지 않는 표시를 건너뛰도록 할 수 있습니다.

let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
  // 분 표시를 5분마다 함(0, 5, 10, 15 ... 50, 55)
}

닫힌 범위 연산자는 또한 stride(from:thorugh:by:) 형태로도 가능합니다.

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval)
    // (3, 6, 9, 12)

 

While 반복문

while 반복문은 조건이 false가 될 때까지 코드를 반복적으로 수행합니다. 반복이 처음 시작 될 때 얼마나 많은 횟수로 반복을 할 것인지를 모를 때 이러한 종류의 반복문이 유용합니다. 스위프트는 두 가지의 while 반복문을 제공합니다.

  • while, 조건식이 매번 반복의 처음에 계산됨
  • repeat-while 조건식이 매번 반복의 마지막에 계산됨

While

while 반복문은 단일한 조건을 계산하여 시작합니다. 이 조건이 true이면 조건이 false가 되기 전까지 코드가 반복됩니다.

여기 while 반복문의 일반적인 형태가 있습니다.

while 조건 {
  실행할 문장
}

이 예제는 _뱀과 사다리_라는 간단한 게임을 실행합니다.

../_images/snakesAndLadders_2x.png

게임의 규칙은 다음과 같습니다.

  • 보드는 25개의 사각형으로 되어있고, 게임의 목표는 25번 째의 사각형에 도착하는 것이다.
  • 게임 참가자는 0번째 사각형부터 시작한다. 이 사각형은 보드의 맨 아래 왼쪽에 벗어나 있다.
  • 각각의 차례마다, 눈이 6개인 주사위를 굴려서 나온 숫자만큼 사각형 번호로 옮겨간다. 위 그림의 점선으로 그려진 수평의 선을 따라가면 된다.
  • 만약 여러분이 사다리 아래에서 끝나면 사다리의 위로 올라간다.
  • 만약 여러분이 뱀의 머리에서 끝나면 뱀의 꼬리로 간다.

게임의 보드는 Int 값 배열로 표현됩니다. 이 것의 크기는 상수인 finalSquare로 명명합니다. 이 상수는 배열을 초기화하고 게임에서 승리할 조건을 판별합니다. 참여자가 보드 밖에서 시작하므로, 보드 배열은 26 개의 Int타입인 0으로 초기화됩니다.

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

3번 사각형은 사다리의 아래를 의미하여 11번 사각형으로 이동해 줍니다. 이것을 표현하기 위해서, board[03]+08과 같습니다. 이 값은 정수 8과 같습니다. 값과 문들을 정렬하기 위해 단항 덧셈 연산자(+i)가 단항 뺄셈 연산자인 (-i)와 함께 명시적으로 사용됩니다.

var square = 0
var diceRoll = 0
while square < finalSquare {
  // 주사위 굴리기
  diceRoll += 1
  if diceRoll == 7 { diceRoll = 1 }
  // 나온 주사위의 값만큼 이동
  square += diceRoll
  if square < board.count {
        square += board[square]
  }
}
print("게임 끝!")

이 예제는 매우 간단한 주사위 굴리기 방법을 사용합니다. 무작위 숫자를 생성하는 대신, diceRoll의 값은 0에서 시작합니다. while반복문이 시행될 때마다, diceRoll은 하나씩 증가하며 이 값이 너무 크지는 않은지 확인됩니다. 7이라는 값이 반환되면 주사위는 1로 초기화됩니다. diceRoll의 연속 값은 항상 1,2,3,4,5,6,1,2,... 로 이어집니다.

주사위를 굴리고 나서, diceRoll의 값 만큼 참가자는 앞으로 움직입니다.참가자가 25를 넘어서 이동할 수 있습니다. 이 경우 게임이 끝나야 합니다. 따라서, squareboard 배열의 count 속성보다 작은지를 확인해야 합니다. square값이 유효하다면 board[square]에 저장된 값은 현재 square값에 추가되며 참가자가 사다리를 오르거나 뱀의 꼬리로 내려갑니다.

참고

이러한 조건문의 검사가 수행되지 않으면, board[sqaure]board 배열의 범위 밖에 있는 값에 접근하려하는 경우이기 때문입니다. 이 경우 런타임에러가 발생합니다.

현재의 while 반복문의 조건은 반복문이 다시 한번 더 실행되어야하는지를 확인하기 위함입니다 참가자가 25 숫자를 넘어서 사각형 번호를 이동한다면 반복문의 조건은 false로 계산되어 게임이 끝나게 됩니다.

while 반복문은 이러한 경우에 적합합니다. 왜냐하면 한번의 게임에서 시도 횟수가 명확하지 않기 때문입니다. 이 반복문은 그 대신, 특정 조건이 만족되기 전까지만 실행됩니다.

Repeat-While

while 반복문의 변형으로 repeat-while 반복문이 있습니다. 이 반복문은 반복문의 조건을 따지기 전에 반복문의 블록을 최초로 한번은 실행하도록 보장합니다. 이후 두번째 반복부터는 조건이 false이기 전까지 반복됩니다.

참고

스위프트의 repaet-while 반복문은 다른 언어의 do-while문과 비슷합니다.

아래는 repaet-while 반복문의 일반적인 형태를 보여줍니다.

repeat {
  실행할 문장
} while 조건

Snakes and Ladders 예제는 while 반복문 대신 repeat-while반복문으로 작성될 수 있습니다. finalSquare, board, square, diceRoll의 값은 while 반복문과 같이 초기화 됩니다.

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

이 게임의 버전에서 반복문의 첫 번째 행위는 사다리인지 뱀인지를 확인하는 것입니다. 사다리를 이용하여 곧바로 25번으로 이동하는 경우는 없으므로 뱀뱀과 사다리가 반복문의 처음에 확인되는 것은 안전합니다.

게임이 시작하면 참가자는 board[0]부터 시작합니다. 이 값은 항상 0이므로 아무런 영향이 없습니다.

repeat {
  // 뱀 또는 사다리를 이용하여 아래 또는 위로 간다.
  square += board[square]
  // 주사위를 굴림
  diceRoll += 1
  if diceRoll == 7 { diceRoll = 1}
  // 나온 값 만큼 이동
  sqaure += diceRoll
} while square < finalSquare
print("Game Over !")

참가자의 위치가 현재 뱀과 사다리에 있는지를 확인하는 코드 이후에 주사위는 굴려지고 참가자는 diceRoll의 값만큼 이동합니다.

이 반복문의 조건인(while square < finalSquare)는 이전과 같지만, 반복문의 가장 처음에 계산되지 않습니다. 이 게임은 while 반복문 보다 repeat-while 반복문의 구조가 훨씬 알맞습니다. repeat-while 반복문의 위의 square += board[square]는 반복문 내부에서 즉시 실행됩니다. 이러한 작업은 이전 while문에서 사용된 배열 길이 체크를 생략하도록 합니다.

조건 문 Conditional Statements

코드를 작성할 때, 조건에 따라 특정 코드를 분기하는 것은 중요합니다. 에러가 발생했을 때 다른 코드의 부분을 실행하길 원할 수도 있고, 값이 너무 크거나 작을 때 메시지를 띄우고 싶을 수도 있습니다. 이것을 위해서는 _조건_에 따라 동작하는 코드를 만들어야 합니다.

스위프트는 조건문을 추가하는 두가지 방법을 제공합니다. ifswitch문이 바로 그것입니다. 일반적으로, 여러분은 if절을 사용하여 가능한 결과가 적은 간단한 조건을 계산할 것입니다. switch문은 가능한 결과가 여럿인 복잡한 조건문에 알맞고, 패턴을 맞추는 것이 적절한 코드 흐름으로 실행되어야 할 때 유용합니다.

If

가장 간단한 if문은 단 하나의 if 조건절을 가집니다. 조건이 true일 때 코드가 실행됩니다.

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
  print("매우 추워. 스카프를 입는게 어때?")
}
// "매우 추워. 스카프를 입는게 어때?"가 출력됨

이 예제는 기온이 화씨 32보다 작거나 같은지를 확인하는 코드입니다. 만약 이 조건절이 사실이라면 메시지는 출력되며, 그렇지 않을 경우 메시지는 출력되지 않습니다. 코드의 실행은 if문의 닫는 중괄호(}) 이후에 계속 이어집니다.

if문은 else절이라고 불리는 것을 가집니다. else절은 if의 조건이 거짓일 경우 실행됩니다.

temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
  print("매우 추워, 스카프를 입는게 어때?")
} else {
  print("그렇게 춥지는 않아. 티셔츠를 입으렴")
}
// "그렇게 춥지는 않아. 티셔츠를 입으렴"이 출력됨

중괄호 안의 코드 둘 중 하나만이 실행됩니다. 왜냐하면, 기온이 화씨 40도로 설정되었으며, 스카프를 입도록 권유할 정도로 춥지 않기 때문에 else절이 실행됩니다.

여러 개의 if문을 함께 사용하여 추가적인 조건을 달수도 있습니다.

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
  print("매우 추워, 스카프를 입는게 어때?")
} else if temperatureInFahrenheit >= 86 {
  print("매우 따뜻해. 선크림 바르는 것을 잊지마.")
} else {
  print("그렇게 춥지는 않아. 티셔츠를 입으렴")
}
// "매우 따뜻해. 선크림 바르는 것을 잊지마."이 출력됨

여기서, 더운 온도에 응답하기 위해 또 다른 if이 추가되었습니다. 너무 춥지도 덥지도 않은 기온에 대답하기 위해, 마지막의 else 절이 작성되었습니다.

else 절은 부가적입니다. 조건문에서 else절을 생략할 수도 있습니다.

temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
  print("매우 추워, 스카프를 입는게 어때?")
} else if temperatureInFahrenheit >= 86 {
  print("매우 따뜻해. 선크림 바르는 것을 잊지마.")
} 

기온이 너무 춥지도 따뜻하지도 않으므로 if 혹은 else if문은 어떠한 출력도 하지 않습니다.

Switch

switch문은 하나의 값을 가지고 여러 가능한 패턴들과 비교합니다. 그리고나서, 패턴이 맞아떨어지면 코드의 블럭이 실행됩니다. switch문은 여러 상태들에 반응할 때 사용됩니다.

switch문의 가장 간단한 형태는 같은 타입의 여러 값들을 비교하는 것입니다.

switch 기준값 {
  case value 1:
          //value 1에 응답하는 코드
  case value 2,
          value 3:
          //value 2 or 3에 응답하는 코드
  default:
        //모두 아니라면, 다른 것을 실행하는 코드

}

모든 swift문은 여러 가능한 경우들(case)로 이루어져 있습니다. 특정한 값을 비교하는 것과 더불어, 스위프트는 복잡한 패턴을 구체화하기 위한 여러 경우들을 제공합니다. 자세한 사항은 본 챕터의 이후에 다룹니다.

if문의 내부와 마찬가지로, case는 코드의 실행의 구별된 가지(branch)입니다. switch문은 어떤 가지가 선택될지를 결정합니다. 이러한 과정을 switching이라고 불립니다.

모든 switch문은 매우 _소모적_입니다. 다시 말해, 어떤 타입의 모든 가능한 값들은 switch의 경우 중 하나에 들어맞아야 합니다. 모든 가능한 값의 경우를 제공하는 것이 여의치 않다면, default라는 키워드를 사용할 수 있습니다. 이 키워드는 switch 절의 가장 마지막에 작성되며 다른 모든 경우를 다룹니다.

아래의 switch문은 하나의 소문자인 someCharacter를 계산합니다.

let someCharacter: Character = "z"
switch someCharacter {
  case "a":
      print("알파벳의 첫 문자")
  case "z" :
      print("알파벳의 마지막 문자")
  defualt:
      print("다른 문자")
}
// "알파벳의 마지막 문자"가 출력됨

switch문의 처음 경우는 영어 알파벳의 첫문자인 a를 비교합니다. 그리고 두 번째 경우는 마지막 글자인 z를 비교합니다. switch는 알파벳 글자뿐만 아니라, 모든 문자의 경우에 대응해야하므로 default를 사용합니다.

비함축적 폴쓰루 (No Implicit Fallthrough)

C와 Objective-C언어의 switch문과 다르게 스위프트의 switch문은 case들의 아래로 폴쓰루를 하지 않습니다. 대신, switch문 전체는 case가 맞아 떨어지면 실행을 마무리합니다. 즉, break문이 필요없습니다. 이러한 문법은 switch문이 더 안전하고 쉽도록 만듭니다.

참고

break문이 스위프트에서 필요는 없지만, 여러분은 여전히 break문을 사용하여 특정 case를 무시하거나 어떤 case의 실행이 종료되기 전에 빠져나올 때 사용할 수 있습니다. 더 자세한 사항은 Break in a Switch Statement을 참고하세요.

모든 case의 몸체는 최소한 하나의 실행가능한 문(statement)를 가지고 있어야 합니다. 아래의 코드는 유효하지 않는 예를 보여줍니다.

let anotherCharacter: Character = "a"
switch anotherCharacter {
  case "a": // 유효하지 않음, case의 몸체가 비어 있음
  case "A":
      print("The letter A")
  default:
      print("Not the letter A")
}
// 이 코드는 컴파일 에러를 발생함

C언어의 switch문과 다르게 이 switch문은 "a", "A" 두 글자 모두 들어 맞지 않습니다. 대신, 컴파일 case "a":이 어떠한 코드를 실행하지 않으므로 컴파일 에러를 발생합니다. 이러한 방법이 실수로 폴쓰루를 만드는 것을 피하도록 해주며, 코드의 의도를 명확하게 해줍니다.

switch문을 하나의 케이스를 가지고 "a""A" 모두를 비교하려면, 복합 케이스에 두 값을 모아주고 컴마로 두 값을 구분하여 줍니다.

let anotherCharacter: Character = "a"
switch anotherCharacter {
  case "a", "A" :
      print("The letter A")
  default:
      print("Not the letter A")
}
// "The letter A" 가 출력됨

가독성을 위해, 복합 케이스문이 여러 줄에 걸쳐 사용될 수 있습니다. 더 자세한 사항은 Compound Cases을 참고하세요,

참고

명시적으로 폴쓰루를 특정 switch 케이스의 끝에 사용하려면, fallthorugh키워드를 사용하세요.

인터벌 매칭

switch case에 오는 값들은 범위를 포함하여 검사할 수 있습니다. 아래의 예제는 어떠한 크기의 숫자든 영어로 바꾸어주는 코드입니다.

let approximateCount = 62
let countedThings = "달이 토성을 공전한다."
let naturalCount: String
switch approximateCount {
  case 0:
      naturalCount = "존재하지 않는"
  case 1..<5:
      naturalCount = "매우 적은 수의"
  case 5..<12:
      naturalCount = "많은 수의"
  case 12..<100:
      naturalCount = "수 십개의"
  case 100..<1000:
      naturalCount = "수백 개의"
  default:
      naturalCount = "매우 많은"
}
print("\(naturalCount) \(countedThings).")
// "수 십개의 달이 토성을 공전한다." 출력

approximateCountswitch문에서 계산되는 예제입니다. 각각의 case는 값을 숫자나 범위에 비교합니다. approximateCount는 12와 100사이에 값이므로 수 십개의로 할당되며 switch문의 실행은 종료됩니다.

튜플 Tuples

튜플을 사용하여 switch문의 여러 값들을 테스트할 수 있습니다. 튜플의 모든 원소들은 다른 값이나 값들의 범위와 비교될 수 있습니다. 또는 와일드카드 패턴인 언더스코어 문자(_)을 사용하여 모든 값들에 맞출 수 있습니다.

아래의 예제는 (Int, Int)타입인 (x,y) 점을 받아서 그래프에 분류합니다.

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)는 사각형 바깥에 있다.")
  default:
      print("\(somePoint)는 사각형 바깥에 있다.")
}
// "(1, 1)은 박스 안에 있다."가 출력됨

../_images/coordinateGraphSimple_2x.png

switch문은 주어진 점이 원점에 있는지 확인하고 x축, y축 그리고 파란색 4x4 박스 내에 있는지 검토하며 마지막으로 박스의 바깥에 있는지 확인합니다.

C 언어와 다르게, 스위프트는 여러개의 switch케이스를 허용하여 같은 값이나 여러 값들을 고려할 수 있습니다. 예제에서 (0, 0)은 4개의 모든 case에 맞습니다. 하지만, 여러 case에 일치할 경우 첫번째 경우만 사용됩니다. (0, 0)은 case(0, 0)에 맞게 되어 다른 case들은 무시됩니다.

값 바인딩

switch문은 임시 상수나 변수와 맞는 값에 이름을 부여하여 해당 case의 내부에 사용할 수 있습니다. 이러한 동작을 값 바인딩(value binding)이라고 합니다. 값들이 case 내부의 임시 상수나 변수에 묶여 있기 때문에 값 바인딩이라는 이름이 붙여졌습니다.

아래의 예제는 (Int, Int)타입의 튜플로 표현되는 (x, y) 점을 받아서, 그래프에 표현합니다.

let anotherPoint = (2,0)
switch anotherPoint {
  case (let x, 0):
      print("x축에 있는 x값 \(x)")
  case (0, let y):
      print("y축에 있는 y값 \(y)")
  case let (x, y):
      print("좌표평면 어딘가에 있는 (\(x), \(y))")
}
// "x축에 있는 x값 2"가 출력됨

../_images/coordinateGraphMedium_2x.png

switch문은 빨간색 x축의 점이 어디있는지 확인하고, 주황색의 y축, 그리고 그 외의 지점으로 판단합니다.

세 개의 switch case는 상수 x와 y를 임시로 선언합니다. 이 상수들은 anotherPoint로부터 하나 혹은 두개의 튜플 값들을 받습니다. 첫 번째 case는 (let x, 0)입니다. 이것은 y값이 0인 점과 맞으며anotherPointx값을 임시 상수 x에 저장합니다.이와 비슷하게 두 번째 case인 case (0, let y)x가 0인 튜플과 맞게되며 그 y값이 임시 상수 y에 저장됩니다.

임시 상수가 선언되고나서, 이것들은 case의 코드 블럭 안에서 사용됩니다.

switch문은 default을 가지지 않습니다. 마지막 case인 case let (x,y)가 두 임시 상수의 튜플을 선언합니다. anotherPoint는 항상 두 개의 값의 튜플이므로 이 case는 위의 case를 제외한 모든 값들과 일치합니다.

Where

switch의 case는 where를 사용할 수 있습니다. 이 where절은 추가적인 조건을 확인하는데 사용됩니다.

아래 그래프에 (x, y) 점을 찍도록 하는 예제를 통해 설명하겠습니다.

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
  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)는 다른 임의의 점이다.")
}
// "\(x), \(y)는 x==-y인 지점에 있다."가 출력됨

../_images/coordinateGraphComplex_2x.png

switch문은 주어진 점이 초록색 대각선, x == y에 있는지 확인하고, 다음에 보라색 대각선, x == -y에 있는지 아니면 그 외에 곳에 있는지 확인합니다.

이 세개의 switch case는 임시 상수 x, y를 선언하여 두 개의 튜플을 yetAnotherPoint로 부터 받습니다. 이 상수들은 where절에 사용되어 동적 필터를 생성합니다. switch casewhere절의 조건이 true이여야 해당 case가 일치하게 됩니다.

이전의 예제에서 마지막 case는 모든 가능한 값들에 일치했습니다. 여기서 또한 default는 낭비이므로 사용되지 않았습니다.

컴파운드 케이스 Compound Case

switch 문에서 case 이후에 여러 패턴을 작성하여 조합할 수 있습니다. 이 떄 각 패턴 사이에 컴마(,)를 작성합니다. 만약 패턴들 중 어느 것이든 일치한다면 해당 case는 일치하는 것으로 판정됩니다. 이 패턴은 여러 줄에 걸쳐 작성되는 것도 괜찮습니다. 예를 들어,

let someCharacter: Character = "e"
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)는 모음도 아니고 자음도 아니다.")
}
// "e는 모음이다."가 출력됨

switch문의 처음 case는 5개의 영소문자 모음과 일치합니다. 이와 같이, 두 번째 case는 영어의 소문자 자음과 일치합니다. 마지막으로 다른 모든 문자들을 일치하도록 default가 있습니다.

컴파운드 케이스는 값 바인딩을 포함할 수도 있습니다. 컴파운드 케이스의 모든 패턴들은 같은 군의 값 바인딩을 포함해야 합니다. 그리고, 각 바인딩은 같은 타입의 값들을 가져야 합니다. 이 규칙을 지키기 위해 , 일치한 컴파운드 케이스의 부분이 어느것이든 case 내부의 코드는 항상 바인딩의 값에 접근할 수 있으며 값은 항상 같은 타입을 갖습니다.

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
  case (let distance, 0), (0, let distance):
      print("축위에 있으며 원 점으로 부터 \(distance)만큼 떨어져 있다.")
  default:
      print("축위에 있지 않다.")
}
// "축위에 있으며 원 점으로 부터 9만큼 떨어져 있다."가 출력됨

이 예제의 case는 두 개의 패턴을 갖습니다. (let distance, 0), (0, let distance)가 그것입니다. 이 패턴은 x축 혹은 y축에 점이 있는지를 검사합니다. 두개의 패턴은 distance라는 바인딩을 가지며 이 값은 두 패턴에서 모두 정수(integer)입니다. case 내부의 코드는 항상 distance의 값에 접근할 수 있습니다. 타입이 다르다면, distance 어느 타입을 사용할지 모르기 때문입니다.

제어 변경 문 Control Transfer Statements

제어 변경 문은 여러분의 코드가 실행되는 순서를 바꿉니다. 스위프트는 5가지의 제어 변경 문을 가지고 있습니다.

  • continue
  • break
  • fallthrough
  • return
  • throw

continue, break 그리고 fallthrough 문은 본 챕터에서 설명합니다. return 문은 함수 (Functions)에서 설명하며, 쓰로잉 함수를 사용한 에러 전이 (Propagating Errors Using Throwing Functions)에서 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)
// "grtmndsthnklk"가 출력됨

위의 코드는 문자가 모음이나 공백과 일치하면 continue 를 호출합니다. 그리고 현재의 반복문의 순회를 즉시 종료하여 다음 순회를 시작합니다.

Break

break 문은 전체 흐름 제어문을 즉시 종료합니다. break문은 switch에 사용하여 case를 건너뛰도록 할 수 있습니다.

반복문에서 Break

반복문 내부에 break는 반복문의 실행을 즉시 종료하고 반복문의 중괄호 (}) 이후의 코드로 제어를 이전합니다.

Switch 문에서 Break

switch 내부에서 break 는 반복문의 실행을 즉시 종료하고 반복문의 중괄호 (}) 이후의 코드로 제어를 이전합니다.

이러한 동작은 switch 문의 여러 case 를 무시하는 데에 사용됩니다. Swift의 switch 문은 비어 있는 case 를 허용하지 않으므로, 여러분의 의도를 명확히 학기 위해 이것을 사용해야 할 수도 있습니다. switch 문에서 case 가 일치하면, 해당 case 에 있는 breakswitch 문을 즉시 종료합니다.

참고

switchcase 가 오직 주석만을 가지고 있다면 컴파일 타임 에러가 발생합니다. 주석은 명령문(statement)가 아니므로 switch 는 이것을 무시하도록 합니다. switchcase 를 무시하도록 하려면 항상 break 문을 사용하세요.

아래의 예제는 4개의 언어로 쓰인 숫자 심볼의 Character 값을 적절한 숫자로 교체합니다. 단순하게 작성하기 위해, 다수의 값이 하나의 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("\(numberSymbol)의 정수 값은 \(integerValue)이다.")
} else {
  print("\(numberSymbol)에 적절한 정수 값을 찾지 못함")
}
// "三의 정수 값은 3이다."

이 예제는 numberSymbol 이 라틴, 아랍, 중국 혹은 타이 기호 인지 확인합니다. 만약 일치하는 것을 찾는다면, switchcase 중 하나가 옵셔널 Int?possibleIntegerValue를 적절한 정수 값으로 할당합니다.

switch문이 실행을 종료하고, 옵셔널 바인딩을 사용하여 값을 찾을 수 있는지 확인합니다. possibleIntegerValue 변수는 암시적 초기 값인 nil을 가집니다. 오직 possibleIntegerValue가 실제 값에 할당될 때만 옵셔널 바인딩이 성공합니다.

위의 예제처럼 모든 가능한 Character값을 나열하는 것은 실용적이지 않으므로, default가 나머지 문자를 다루도록 합니다. 이 default는 어떠한 동작도 필요하지 않으므로 하나의 break를 가집니다. 코드 실행 중 default를 만나게 되면, break 문이 즉시 switch 문을 종료하고 if let문으로 코드가 진행됩니다.

폴쓰루 Fallthrough

스위프트에서 switch문은 각각의 case 의 아래까지 폴쓰루(내려감)하지 않습니다. 즉, 전체 switch 문이 처음의 case가 일치되고 그 내부의 실행이 종료되며 해당 switch은 즉시 종료됩니다. 이와 반대로, C언어는 폴쓰루를 방지하기 위해 모든 switch 문에 break를 명시적으로 입력해야 합니다. 기본 폴쓰루를 피함으로써 Swift의 switch 문이 C보다 좀 더 명확하고 예측가능하다는 것입니다.

만약 여러분이 C 방식의 폴쓰루 방식을 원한다면 fallthrough 키워드를 사용할 수 있습니다. 아래의 예제는 fallthrough를 사용하여 수를 분류합니다.

let integerToDescribe = 5
var description = "\(integerToDescribe)는 "
switch integerToDescribe {
  case 2, 3, 5, 7, 11, 13, 17, 19:
      description += " 소수이며, "
      fallthorugh
  default :
      description += "정수이다."
}
print(description)
// "5는 소수이며, 정수이다."가 출력됨

이 예제는 새로운 String 변수인 description을 선언하여 초기 숫자를 할당합니다. 그리고 switch문은 integerToDescribe의 값을 판단합니다. 만약 integerToDescribe가 나열된 값의 소수 중 하나라면 description이라는 변수를 덧붙여 그 숫자가 소수임을 말해줍니다. 그리고 나서, fallthorugh 키워드를 만나서 default로 이동하게 됩니다. defaultdescription의 끝에 또 다른 텍스트를 추가합니다. 이제 switch문은 종료됩니다.

integerToDescribe의 값이 코드에서 작성된 소수에 있지 않다면 첫 번째 switch case에 일치하지 않습니다. 이것 이외에 다른 경우는 작성되지 않았으므로 integerToDescribedefault와 일치하게 됩니다.

switch 문이 실행을 종료하면 print(_:separator:terminator:) 함수가 description을 출력하게 됩니다. 이 예제에서는 숫자 5가 소수입니다.

참고

fallthorugh는 그 키워드를 실행한 case의 경우는 확인하지 않습니다. fallthrough 키워드는 단순히 코드의 실행을 다음의 case(혹은 default)로 이동합니다. 이러한 동작은 C언어의 switch와 동일합니다.

명령문 표식

스위프트에서 여러분은 반복문을 중첩하거나 다른 반복문안에 조건 문을 중첩하여 복잡한 흐름 제어 구조를 만들 수 있습니다. 하지만 반복문과 조건문은 모두 break문을 사용하여 미리 실행을 종료할수도 있습니다. 따라서, 어떤 반복문 혹은 조건 문을 명시적으로 하여 종료하는 것은 유용하게 사용됩니다. 여러 중첩된 반복문을 사용한다면 어떠한 반복문으로 continue문을 적용할지를 명시적으로 할 수 있습니다.

이러한 목표를 달성하려면, 명령문 표식을 사용할 수 있습니다. 조건문에서 break을 가지고 명령문 표식을 사용하면 표식이 적용된 명령문의 실행을 종료할 수 있습니다.

명령문의 시작 키워드와 같은 줄에 명령문 표식을 적용할 수 있습니다. 아래의 while 반복문의 문법은 switch문과 모든 반복문에도 똑같은 원칙이 적용됩니다.

반복문 이름: while 조건 {
    명령문
}

이 예제는 표식을 적용한 while 반복문에서 breakcontinue을 사용합니다. 또한, 이 에제는 이 챕터에 보았던 뱀과 사다리 게임의 또 다른 버전입니다. 이번에는 또다른 규칙이 적용됩니다.

  • 게임을 승리하기 위해 25번째 사각형에 정확히 위치해야 합니다.

25번 사각형을 넘어가면, 다시 한번 주사위를 굴려야 합니다.

게임의 보드는 이전과 같습니다.

../_images/snakesAndLadders_2x.png

finalSquare, board, sqaure, 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

이번 게임의 버전은 whileswitch문을 사용하여 게임의 로직을 구현합니다. while 반복문은 표식을 가진 gameLoop이라는 명령문을 가지고 있습니다. 이것은 이 게임의 메인 반복문으로 사용됩니다.

while sqaure != finalSquare는 여러분이 정확히 25번 사각형에 착지해야 함을 의미합니다.

gameLoop: while square != finalSquare {
  diceRoll += 1
  if diceRoll == 7 { diceRoll = 1 }
  switch square + diceRoll {
    case finalSqaure:
        // diceRoll이 사용자를 마지막 숫자로 이동하였습니다. 게임이 종료됩니다.
        break gameLoop
    case let newSqaure where newSqaure > finalSqaure:
        // diceRoll이 사용자를 마지막 숫자를 넘어서 이동했습니다. 다시 주사위를 굴립니다.
        continue gameLoop
    default:
        square += diceRoll
        square += board[square]
  }
}
print("Game Over!")

각 반복문의 시작부터 주사위가 굴려지게 됩니다. 참가자를 즉시 이동시키기 보다, switch문이 이동한 결과를 파악하여 해당 이동 지점이 가능한지를 판단합니다.

  • 주사위가 참가자를 마지막 지점으로 이동한다면 게임은 끝납니다. break gameLoop 명령문은 while 반복문의 바깥의 처음 코드로 코드 제어를 이동합니다. 이로써 게임이 끝나게 됩니다.

  • 주사위가 마지막 숫자를 넘어서 이동하게 된다면 해당 지점은 유효하지 않으므로 사용자는 다시 주사위를 굴립니다. continue gameLoop 명령문은 현재 while문을 종료하고 반복문의 다음 순회를 시작합니다.

  • 나머지 경우의 경우 주사위의 숫자는 유효합니다. 참가자는 diceRoll에 의해 이동되고 게임의 로직은 그 숫자가 뱀이거나 사다리에 해당하는지를 확인합니다. 반복문이 끝나면, while의 조건 문으로 돌아가서 또 다른 행동이 필요한지를 결정합니다.

    참고

    위 예제의 break 명령문은 gameLoop 표식을 사용하지 않습니다. switch 명령문을 빠져나오지만 while문은 빠져나오지 않습니다. gameLoop 표식을 사용함으로써 어떤 제어 문이 종료되어야하는지를 명확하게 합니다.

    continue gmaeLoop을 호출하여 반복문의 다음 순회로 이동하기 위해 gameLoop 표식을 사용하는 것은 반드시 강제되는 것은 아닙니다. 위 예제는 하나의 반복문이 있으므로 continue 문이 어느 반복문에 적용될지를 파악하는 것이 모호하지 않습니다. 하지만 gameLoop 표식을 사용하는 것은 이 게임의 로직을 읽기 쉽고 이해하기 쉽도록 해줍니다.

조기 탈출 Early Exit

gaurd 명령문은 if문과 비슷하게, 표현식의 Boolean 값에 따라 실행을 결정하게 됩니다. guard 명령문이 실행되고 나서 조건이 반드시 참이여야 할때 gaurd 명령문을 사용하세요. gaurd 명령문이 if문과 다른점은 항상 else 절을 가져야한다는 것입니다. 이 절안의 코드는 조건이 참이 아닐 때 실행됩니다.

func greet(person: [String: String]) {
  guard let name = person["name"] else {
    return
  }
  print("Hello \(name)!")

  guard let location = person["location"] else {
    print("I hope the weather is nice near you")
    return
  }
  print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertion"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

만약 guard 명령문이 조건을 만나면 코드 실행은 guard 명령문의 닫는 중괄 이후에 계속됩니다. 옵셔널 바인딩으로 사용되어 값이 할당되는 어떠한 변수나 상수든 guard가 나타나는 코드 블럭의 나머지에 사용 가능합니다.

만약 조건이 맞지 않는다면, else 절의 코드가 실행됩니다. 이 절의 코드는 guard 명령문의 코드블럭을 빠져나오기 위해 반드시 제어를 이전해야 합니다. 이것은 또한 return, break, continue 또는 throw 와 같은 제어 이전 명령문에도 사용됩니다.

guard 명령문을 코드의 가독성을 향상하기 위해 사용한다면 if 문과 비교해보세요. guard 명령문은 else 블럭에서 래핑할 필요가 없는 것을 실행하는 코드를 작성하도록 해줍니다. 또한, 코드가 적합하지 않은 조건을 다루도록 해줍니다.

API 사용 가능성 검사

스위프트는 내재된 API 사용 가능성 검사를 지원합니다. 이 것은 우연히 사용 불가능한 API를 사용하지 못하도록 보장해줍니다.

컴파일러는 SDK 내부의 사용 가능성 정보를 사용하여 여러분의 코드에서 사용도니느 모든 API를 검사합니다. 스위프트는 사용 불가능한 API를 사용할 때 컴파일 시점에 에러를 출력합니다.

ifguard 명령문 안에서 _사용 가능성 조건(availability condition)_을 사용하여 조건에 따라 코드의 블럭을 실행하세요. 이 떄 런타임 시점에 API를 사용 가능한가에 따라 실행됩니다.

if #available(iOS 10, macOS 10.12, *) {
  // iOS 10의 APIs를 사용하세요. macOS 10.12의 API를 사용하세요.
} else {
  // 사용하지 않습니다.
}

위 예제의 사용 가능성 검사는 iOS에서의 가능성을 검사합니다. if 명령문의 내부는 iOS10 이후, macOS 10.12 이후에서만 실행됩니다. 마지막 인자인 *는 모든 플랫폼을 의미합니다.

일반적인 형태에서 사용 가능성 검사는 플랫폼의 이름과 버전을 이용합니다. 플랫폼의 이름은 iOS, watchOS, tvOS 등이 있습니다. 모든 리스트는 Declaration Attributes에서 확인할 수 있습니다. iOS8, macOS 10.10 처럼 명시적으로 메이저 버전을 기재하거나 iOS 11.26, macOS 10.13.3 처럼 마이너 버전 또한 기재할 수 있습니다.

if #available(플랫폼 이름 버전, ..., *) {
  API가 가능하다면 실행할 명령문
} else {
  API가 불가능하다면 실행할 명령문
}

베타 소프트웨어

이 문서는 개발 API의 베타 정보를 포함합니다. 이 정보들은 변경될 수 있으며 테스트 되어 사용되어야 합니다.


https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html