Swift는 다양한 제어 흐름(control flow) 구문을 제공합니다. 여러번 작업을 수행하는 for-in 루프, while 구문과 조건에 따라 다르게 실행되는 if, guard, switch 구문과 다른 포인트로 실행 포인트로 전달하는 break와 continue 문이 있습니다. 또한, Swift는 범위를 벗어났을 때 실행되는 코드인 defer 구문을 제공합니다. 또한, 복잡한 매칭 조건은 각 케이스에 대해 where 절로 표현할 수 있습니다.
For-In 루프 (For-In Loops)
배열에 아이템, 범위의 숫자, 또는 문자열에 문자와 같은 연속된 것에 대해 for-in 루프를 사용하여 반복할 수 있습니다.
아래 예제는 for-in 루프를 사용하여 배열의 아이템을 반복합니다.
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
Dictionary 반복
딕셔너리의 키-값 쌍 접근을 위해 반복을 사용할 수도 있습니다. 딕셔너리의 각 아이템은 딕셔너리가 반복될 때 (key, value) 튜플로 반환되고, for-in 루프 본문 내에서 사용하기 위해 (key, value) 튜플의 멤버를 명시적으로 이름을 가진 상수로 분해할 수 있습니다. 아래 예제에서 딕셔너리의 key는 animalName 상수로 분해되고 딕셔너리의 value는 legCount 상수로 분해됩니다.
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs
Dictionary 의 콘텐츠는 기본적으로 순서가 없고 반복으로 가져올 아이템에 대한 순서를 보장하지 않습니다. 특히 아이템을 딕셔너리에 삽입하는 순서로는 아이템이 반복되는 순서를 정의할 수 없습니다.
아래 예제는 숫자 범위에 대해 for-in 루프를 사용할 수도 있습니다. 이 예제는 5의배수 항목을 몇개 출력합니다.
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
이 반복 시퀀스는 닫힌 범위 연산자(...)를 사용하여 1 부터 5 까지의 숫자 범위를 반복합니다.
위의 예제에서 index는 루프의 각 반복이 시작할 때 자동으로 설정되는 값인 상수입니다. 따라서 index를 사용하기 전에 선언할 필요가 없습니다.
언더바(_) 사용
또한, 아래처럼 시퀀스로부터 각 값이 필요하지 않으면 변수 이름의 위치에 언더바(_)를 사용하여 값을 무시할 수 있습니다.
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049"
반열림 범위 연산자(..<)
어떤 상황에서는 양 끝점을 포함하는 닫힌 범위를 사용하지 않을 수 있습니다. 시계에 매 분마다 눈금을 그리는 것을 생각해봅시다. 이럴 경우에는 아래 예시처럼 반열림 범위 연산자(..<)를 사용하여 가장 최소 값은 포함하지만 최대 값은 포함되지 않게 사용할 수 있습니다.
let minutes = 60
for tickMark in 0..<minutes {
// render the tick mark each minute (60 times)
}
stride(from:to:by:) 함수
어떤 사용자는 UI에 더 적은 눈금을 원할 수도 있습니다. 예를 들어 매 5분마다 눈금을 그리기 원할 수도 있습니다. 원하지 않은 눈금을 건너뛰기 위해 stride(from:to:by:) 함수를 사용할 수 있습니다.
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
}
stride(from:to:by:)를 사용하여 닫힌 범위도 사용 가능합니다.
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// render the tick mark every 3 hours (3, 6, 9, 12)
}
위의 예제들은 범위, 배열, 딕셔너리, 그리고 문자열을 조회하기 위해 for-in 루프를 사용합니다.
While 루프 (While Loops)
while 루프는 조건이 false가 될 때까지 구문을 수행합니다. 이러한 루프는 첫번째 반복이 시작되기 전에 반복 횟수를 알 수 없을 때 가장 잘 사용됩니다. Swift는 두 종류의 while 루프를 제공합니다.
- while은 루프가 시작할 때마다 조건을 비교합니다.
- repeat-while은 루프가 끝날 때 마다 조건을 비교합니다.
While
while 루프는 단일 조건의 평가를 통해 시작합니다. 조건이 true 이면 조건이 false가 될 때까지 구문은 반복됩니다.
아래는 while 루프의 기본 형식입니다.
while <#condition#> {
<#statements#>
}
Repeat-While
repeat-while 루프는 루프의 조건을 판단하기 전에 루프 블럭을 처음에 한번은 무조건 실행하고, 조건이 false가 될 때까지 루프를 반복합니다.
repeat {
<#statements#>
} while <#condition#>
조건 구문 (Conditional Statements)
조건이 포함하는 코드 기반의 다른 조각을 실행할 때 유용한 경우가 있습니다. 에러가 발생하거나 값이 너무 크거나 작을 때 메세지를 출력하려고 할 때 코드의 별도의 부분을 실행하고 싶을 수 있습니다. 이러한 동작을 위해 코드 조건부를 만들어야 합니다.
Swift에서 코드에 조건부를 추가하는 방법은 if 구문과 switch 구문 2가지를 제공합니다. 일반적으로 if 구문은 가능한 결과가 적은 간단한 조건에 적합합니다. switch 구문은 가능한 결과가 여러개이고, 더 복잡한 조건에 적합하고 실행해야 할 적절한 코드 분기를 선택해야 하는 패턴 매칭에 유용합니다.
If 구문
가장 간단한 형식으로 if 구문은 단일 if 조건을 갖습니다. 조건이 true일 경우에만 구문을 실행합니다.
아래 예시는 기온이 화씨 32보다 작거나 같은지를 확인합니다. 만약, 화씨 32보다 작거나 같으면 메시지가 출력되고 스카프를 해야한다는 메시지가 출력되고, 32보다 크면 추워서 T-shirt를 입어야 한다는 메시지가 출력됩니다.
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else {
print("It's not that cold. Wear a T-shirt.")
}
// Prints "It's not that cold. Wear a T-shirt."
혹은, if 표현식에서 할당 구문을 포함할 수 있습니다. 아래와 같이 freezeWarning은 옵셔널 값으로 선언하여 if문의 하나의 분기는 문자열 값을 반환하도록 하고, 다른 분기는 nil을 반환하도록 명시하였습니다.
let freezeWarning: String? = if temperatureInCelsius <= 0 {
"It's below freezing. Watch for ice!"
} else {
nil
}
Switch 구문
switch 구문은 값을 고려한 가능한 여러개의 패턴과 비교합니다. 그런 다음 첫번째로 일치하는 패턴을 기반으로 적절한 코드 블럭을 실행합니다. 그래서, switch 구문은 if 구문의 대체 구문으로 사용하기도 합니다.
가장 간단한 형식으로 switch 구문은 하나 이상의 같은 타입의 값과 비교합니다.
switch <#some value to consider#> {
case <#value 1#>:
<#respond to value 1#>
case <#value 2#>,
<#value 3#>:
<#respond to value 2 or 3#>
default:
<#otherwise, do something else#>
}
모든 switch 구문은 각각 case 키워드로 시작하는 여러개의 케이스로 구성되어 있습니다. 특정 값과 비교하는 것 외에도 더 복잡한 패턴을 지정하는 여러가지 방법도 존재합니다. 모든 값은 siwtch 케이스 중 하나와 반드시 일치해야 합니다.
아래 예시는 a와 z를 제외한 다른 모든 문자는 default 케이스를 사용하게 됩니다.
let anotherCharacter: Character = "a"
let message = switch anotherCharacter {
case "a":
"The first letter of the Latin alphabet"
case "z":
"The last letter of the Latin alphabet"
default:
"Some other character"
}
print(message)
// Prints "The first letter of the Latin alphabet"
명시적 Fallthrough (No Implicit Fallthrough)
C와 Objective-C의 switch 구문과는 다르게 break 구문이 없어도 처음 일치하는 switch 케이스가 완료되자마자 switch 구문 전체가 끝납니다. 이러한 점은 switch 구문을 더 안전하게 사용하기 쉽게 해주고, 실수로 switch 케이스가 하나 이상 실행되는 것을 피할 수 있습니다.
그래서 각 케이스의 본문은 반드시 적어도 하나의 실행가능한 구문이 포함되어야 합니다. 아래의 코드는 첫번째 케이스가 비어있으므로 유효하지 않습니다.
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// This will report a compile-time error.
case "a": 에 어떠한 실행가능한 구문이 없기 때문에 컴파일 에러가 발생합니다. 이러한 접근 방식은 한 케이스에서 다른 케이스로의 우발적인 실행을 방지하고 의도를 명확하고 안전한 코드로 만들어줍니다.
"a"와 "A" 모두 일치하는 단일 케이스의 switch를 만드려면 두 값을 콤마로 구분하여 하나로 결합하여 구성합니다.
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// Prints "The letter A"
간격 일치 (Interval Matching)
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."
위의 예시에서 approximateCount의 값은 12와 100 사이에 속하므로 naturalCount는 "dozens of" 값이 할당되고 switch 구문을 빠져나옵니다.
튜플 (Tuples)
같은 switch 구문에 튜플을 사용할 수 있습니다. 가능한 값을 일치하도록 와일드 카드 패턴(wildcard pattern)으로 알려진 언더바 문자(_)를 사용할 수 있습니다.
아래 예시는 튜플 타입 (Int, Int)으로 나타내는 좌표 점(x, y) 대한 switch 문입니다.
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint) is at the origin")
case (_, 0):
print("\(somePoint) is on the x-axis")
case (0, _):
print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
print("\(somePoint) is inside the box")
default:
print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box"
위 예시의 Switch 구문은 점이 원점 (0, 0) 인지, x축 위에 있는지, y축 위에 있는지, 아니면 원점이 중심인 4 x 4 박스에 있는지를 판단합니다. 사실 점 (0, 0)은 예시의 모든 4개의 케이스와 일치합니다. 그러나 여러개가 일치할 수 있다면 첫번째 일치하는 케이스가 사용되고 전체 swich 문은 종료됩니다.
값 바인딩 (Value Bindings)
switch 케이스는 일치하는 값들을 임시적 상수 또는 변수로 가질 수 있으며, 케이스 본문 안에서 사용할 수 있습니다. 값은 케이스의 본문 내부에서 임시적 상수 또는 변수로 바인딩되기 때문에 이러한 동작을 값 바인딩(value binding)이라고 합니다.
아래 예시는 튜플의 타입 (Int, Int)로 표현된 점(x, y)를 가집니다.
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"
위 switch 구문은 점이 x축에 있는지, y축에 있는지, 또는 아예 다른곳에 있는지를 판단합니다.
이 3개의 switch 케이스는 anotherPoint에서 나오는 임시적인 값(let x, let y)들을 받습니다. 이 switch 구문은 default 케이스를 가지고 있지 않습니다. 마지막 케이스 case let (x, y)는 어떠한 값도 일치할 수 있는 2개의 상수를 가지는 튜플로 선언합니다. anotherPoint는 2개 값의 튜플이기 때문에 가능한 남아있는 모든 케이스와 일치하며 switch 구문을 완벽하게 하기 위해 default 케이스가 필요하지 않습니다.
Where 절
switch 케이스는 추가 조건으로 where 절을 사용할 수 있습니다.
아래 예시는 그래프에 (x, y)로 분류합니다.
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y"
위 switch 구문은 x == y 인 대각선에 있는지, x == -y 인 대각선 위에 있는지, 아니면 그 외에 위치하는지 판단합니다.
3개의 switch 케이스는 yetAnotherPoint 에서 임시적으로 2개의 튜플 값을 가지는 x와 y 상수를 선언합니다. 이 상수들은 다이나믹한 필터링을 하기위해 where 절에서 사용됩니다. siwtch 케이스는 where 절의 조건이 true 일 때만 point의 현재 값과 일치하게 됩니다.
마찬가지로, 마지막 케이스는 가능한 남아있는 모든 값들과 일치하게 되므로 default 케이스가 필요하지 않습니다.
합성 케이스 (Compound Cases)
같은 본문 코드를 공유하는 스위치 케이스는 case 다음에 각 패턴 사이에 콤마로 구분하여 어러개의 패턴으로 결합될 수 있습니다. 여러 패턴 중 어떤 패턴이 일치하면 해당 케이스를 실행하고 종료합니다. 패턴이 길면 여러 줄로 작성할 수 있습니다.
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(someCharacter) is a consonant")
default:
print("\(someCharacter) is not a vowel or a consonant")
}
// Prints "e is a vowel"
switch 구문의 첫번째 케이스는 영어의 5개의 소문자 모음과 일치합니다. 비슷하게 두번째 케이스는 모든 영어 소문자 자음이 일치합니다. 마지막으로 default 케이스는 나머지 모든 문자와 일치합니다.
또한, 혼합 케이스는(Compound cases)는 값 바인딩을 포함할 수도 있습니다. 혼합 케이스의 모든 패턴은 값 바인딩의 같은 집합을 포함해야 하고 각 바인딩은 혼합 케이스에 모든 패턴에서 같은 타입이어야 합니다.
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("On an axis, \(distance) from the origin")
default:
print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"
위의 case는 2개의 패턴을 가지고 있습니다. (let distance, 0) 은 x축 위의 점을 나타내고, (0, let distance)는 y축 위의 점과 일치합니다. 두 패턴 모두 distance에 대한 값 바인딩을 가지고 있으며, 모두 정수 타입이비다.
제어 변경 구문 (Control Transfer Statements)
제어 변경 구문 (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 in a Switch Statement)
switch 구문 내에서 사용할 때 break는 전체 switch 구문을 종료하고 switch 구문의 닫힌 중괄호(}) 다음으로 이동시킵니다.
Swift의 switch 구문은 빈 케이스를 허락하지 않기 때문에 의도를 명시하기 위해 의도적으로 케이스를 일치시키고 무시해야 하는 경우가 있습니다. 무시할 케이스의 전체 본문으로 break 구문을 작성하여 이를 수행합니다. 해당 케이스가 switch 구문과 일치하면 케이스 내부의 break 구문이 switch 구문의 실행을 즉시 종료합니다.
Fallthrough
Swift에서 switch 구문은 각 케이스의 맨 아래에서 다음 케이스로 넘어가지 않습니다. 첫번째 케이스가 일치하자마자 switch 구문의 실행은 완료됩니다. 반대로 C는 다음 케이스로 넘어가는 것을 막기 위해 모든 switch 케이스 마지막에 명시적으로 break 구문을 명시적으로 넣어야 합니다. 그런데, 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 변수를 선언하고 초기값을 할당합니다. intergerToDescribe 값이 리스트에 있는 소수 중 하나라면 소수라는 것은 나타내는 텍스트를 description 뒤에 추가합니다. 그러면 fallthrough 키워드를 사용하여 default 케이스 또한 동작하게 됩니다. default 케이스는 추가 설명을 덧붙이고 switch 구문을 완료합니다.
이른 종료 (Early Exit)
if 구문과 같이 guard 구문은 표현식의 부울 값에 따라 구문을 실행합니다. guard 구문은 guard 구문의 다음을 실행하기 위해 반드시 조건이 참인 것을 요구하기 위해 사용합니다. if 구문과 반대로 guard 구문은 항상 else 절을 가지고 있습니다. 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": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."
guard 구문의 조건이 충족되면 코드는 guard 구문의 닫는 중괄호의 다음으로 이어집니다. 조건의 일부로 옵셔널 바인딩을 사용하여 값이 할당된 모든 변수 또는 상수는 guard 문이 존재하는 나머지 코드 블럭에서 사용할 수 있습니다.
조건이 충족되지 않으면 else 안의 코드가 실행됩니다. 이 블록은 반드시 guard 구문이 존재하는 코드 블록을 종료하기 위해 제어를 이동해야 합니다. 필요 조건을 위한 guard 구문의 사용은 if 구문과 비교하여 코드를 더 읽기 좋은 코드로 작성할 수 있습니다. 이를 통해 일반적으로 실행되는 코드를 else 블록으로 래핑하지 않고 작성할 수 있으며 요구에 충족되지 않는 부분을 요구사항 근처에서 처리할 수 있도록 해줍니다.
연기된 동작 (Deferred Actions)
코드의 일부분이 실행되거나 실행 횟수를 제어할 수 있는 if와 while과 같은 제어 흐름 구조와 다르게 defer는 코드 일부분이 실행되는 시기를 제어합니다. 현재 범위의 마지막에 실행되는 코드블록을 작성하기위해 defer 블록을 사용합니다.
var score = 1
if score < 10 {
defer {
print(score)
}
score += 5
}
// Prints "6"
위의 예시에서 defer 블록 안의 코드는 if 구문의 본문이 종료되기 전에 실행됩니다. 먼저 score를 5 증가시키는 if 구문의 코드가 실행됩니다. 그런다음 if 구문의 범위가 종료되기 전에 score를 표시하는 연기된 코드가 실행됩니다.
프로그램이 어떻게 종료하는지에 관계 없이 defer 안의 코드는 항상 실행됩니다. 여기에는 함수의 이른 종료, for 루프의 중단 또는 에러를 던지는 동작도 포함됩니다. 수동으로 메모리 할당과 해제, 저수준 파일 열기와 닫기, 데이터베이스에서 트랜잭션 시작과 종료와 같은 동작에서 defer는 이러한 동작을 나란히 작성할 수 있기 때문에 쌍으로 수행되는 동작을 보장해야할 때 유용합니다.
아래 예시는 점수에 100을 더하고 뺌으로써 임시로 보너스를 주는 코드입니다.
var score = 3
if score < 100 {
score += 100
defer {
score -= 100
}
// Other code that uses the score with its bonus goes here.
print(score)
}
// Prints "103"
같은 범위에 하나 이상의 defer 블록을 작성하면 첫번째 defer 블록은 마지막에 실행됩니다.
if score < 10 {
defer {
print(score)
}
defer {
print("The score is:")
}
score += 5
}
// Prints "The score is:"
// Prints "6"
예를 들어 런타임 에러나 충돌로 인해 프로그램 수행이 멈추면 연기된 코드는 수행되지 않습니다. 그러나 연기된 코드는 에러가 발생된 후에 실행됩니다.
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/
Documentation
docs.swift.org