공식 문서로 공부하는 Swift (4) - 제어문

ci·2020년 5월 25일
1

Control Flow

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!

딕셔너리 또한 (key, value) 형태의 튜플로 순회할 수 있으며, 튜플의 요소를 블록 내에서 사용하기 위해 명시적인 이름의 상수로 만들어 줄 수 있다.

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

딕셔너리는 순서가 없기 때문에 순회를 할 때 순서대로 검색이 될 것이라는 보장을 하지 못한다. 삽입한 순서가 순회에서의 순서를 정의하지 않는다.


숫자의 범위를 지정해 반복문을 사용할 수 있다.

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

이 예제에서 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"

반 열린 범위 연산자를 사용하여 lower bound는 포함하지만 upper bound는 포함하지 않는 범위를 지정할 수 있다.

let minutes = 60
for tickMark in 0..<minutes {
    // render the tick mark each minute (60 times)
}

반 열린 범위 연산자에 간격을 설정하고 싶을 경우 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:through: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)
}


While 반복문

while 반복문은 조건이 거짓이 될 때까지 구문을 실행한다. Swift는 두 가지 형태의 while문을 제공한다.

  • while : 각 반복을 시작할 때 조건을 판단한다.
  • repeat-while : 각 반복이 끝날 때 조건을 판단한다.

While

while문은 하나의 조건 판단함으로써 시작한다. 만약 조건이 참이라면, 조건이 거짓이 될 때까지 조건이 반복된다.

while문은 다음과 같이 작성한다.

while condition {
  statements
}

1부터 25까지의 숫자와 몇 가지 함정으로 이루어진 보드 게임이 있다고 가정하자.

// 게임판 초기화
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 square < finalSquare {
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
    if square < board.count {
        // if we're still on the board, move up or down for a snake or a ladder
        square += board[square]
    }
}
print("Game over!")

Repeat-While

repeat-while문은 처음 한 번 구문을 실행한 뒤 조건을 검사한다. 이후 조건이 거짓이 될 때까지 반복문을 실행한다.

다른 언어의 do-while문과 같은 역할을 한다.

repeat-while문의 일반적인 형태는 다음과 같다.

repeat {
    statements
} while condition

repeat {
    // move up or down for a snake or ladder
    square += board[square]
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
} while square < finalSquare
print("Game over!")

조건문이 끝에서 평가되기 때문에, square가 범위를 초과하는지 확인할 필요가 없다.



조건문

Swift는 두 가지 형태의 조건문을 제공한다.

  • if : 몇 가지 가능한 결과에 대한 간단한 조건을 판단하는 데 사용한다.
  • switch : 보다 복잡한 조건에 적합하다. 또한 패턴 매칭이 실행할 코드 분기를 선택하는 데 도움이 될 수 있는 경우 유용하다.

If

가장 간단하게, if 조건 하나만으로 표현할 수 있다. 조건이 참일 경우 실행된다.

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
}
// Prints "It's very cold. Consider wearing a scarf."

조건이 거짓일 때 실행되어야 하는 구문이 존재할 경우 else 키워드를 사용한다.

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

다수의 조건이 있다면 else if를 사용하여 분기한다.

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}
// Prints "It's really warm. Don't forget to wear sunscreen."

Switch

switch 구문은 몇 가지 매칭이 가능한 패턴과 대조하여 값을 판단한다. 처음으로 패턴과 매칭이 되는 곳에서 코드가 실행된다.

다음과 같은 형태에서, 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문에서 모든 가능한 값들은 반드시 케이스 중 하나와 매칭이 되어야 한다. 만약 모든 값에 대해 케이스를 제공하지 못할 경우, default 케이스를 반드시 설정해야 한다. 이는 마지막에 위치해야만 한다.

let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the alphabet")
case "z":
    print("The last letter of the alphabet")
default:
    print("Some other character")
}
// Prints "The last letter of the alphabet"

암시적 진행을 사용하지 않음 (No Implicit Fallthrough)

C 또는 Objective-C와 반대로, Swift의 switch 구문은 암시적인 진행을 하지 않는다. C나 Objective-C에서는 기본적으로 모든 케이스를 순회하여 default를 만날 때까지 진행된다. 이를 위해 break 키워드를 명시해야 했다. Swift에서는 break를 적지 않아도 특정 케이스가 완료되면 자동으로 switch구문을 빠져 나오게 된다. 이런 사용법으로 인해 실수로 break를 빠뜨려 의도하지 않은 케이스가 실행되는 것을 방지해 준다.

break가 Swift에서 필수적이지는 않지만, 케이스 안의 특정 지점에서 멈추도록 하기 위해 break를 사용할 수 있다.


각각의 케이스 안에는 반드시 실행 가능한 구문이 있어야 한다. 다음의 코드는 첫 번째 케이스가 비어 있기 때문에 컴파일 에러를 발생시킨다.

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.

C와는 다르게 Swift는 'a'와 'A'에 모두 매칭되지 않으며, 컴파일 시간에 에러를 일으킨다.


콤마(,)를 사용해 동일한 구문을 실행하는 여러가지 케이스를 결합할 수 있다.

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

가독성 때문에 혼합해 사용하는 경우를 여러 코드라인에 나눠서 적을 수도 있다.

명시적으로 switch-case 문의 특정 지점의 끝까지 실행하고 싶다면 fallthrough 키워드를 사용할 수 있다.


인터벌 매칭 (Interval Matching)

숫자의 특정 범위를 조건으로 지정할 수 있다.

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

튜플

동일한 구문을 실행하는 다양한 값을 테스트하기 위해 튜플을 사용할 수 있다. 튜플 내의 각각의 원소가 다른 값 또는 범위와 맞는지 검사된다. 언더스코어(_)를 사용할 수 있는데, 이는 가능한 모든 값에 매치되는 와일드카드 역할을 한다.

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"

값 바인딩 (Value Binding)

switch 케이스는 케이스 안에서 사용할 임시적인 상수, 변수에 매치되는 값 또는 값들을 명명할 수 있다.

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"

위 예제의 첫 번째 케이스에서, x에 매치되는 값(2)이 임시 상수인 x에 할당된다. 이렇게 임시 상수가 선언된 후에 케이스의 코드 블록 안에서 사용할 수 있게 된다.

default 케이스가 존재하지 않는데, 마지막 케이스인 case let (x, y)는 가능한 모든 값과 매치가 되기 때문이다.


Where

추가적인 조건을 확인하기 위해 where절을 사용할 수 있다.

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"

위의 예시에서 세 가지 케이스는 yetAnotherPoint로부터 두 튜플 값들을 임시적으로 가져올 placeholder 상수 x와 y를 선언한다. 이 상수들은 동적 필터로 생성되어 where 구문의 일부분으로써 사용된다.


혼합 케이스

동일한 실행부를 공유하는 여러 케이스들을 콤마로 구분하여 작성할 수 있다. 하나라도 패턴에 매치되면 그 케이스가 실행된다. 목록이 길 경우 여러 줄에 나눠서 작성할 수 있다.

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"

혼합 케이스는 값 바인딩을 포함할 수 있다. 혼합 케이스의 모든 패턴은 반드시 값 바인딩의 셋이 동일해야 한다. 혼합 케이스의 모든 패턴으로부터 동일한 타입의 값을 얻을 수 있어야 한다. 케이스의 값 바인딩 중 하나만 일치해도 구문이 실행된다.

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"


제어 전송 구문 (Control Transfer Statements)

제어 전송 구문(Control Transfer Statements)는 제어권을 코드의 한 부분에서 다른 부분으로 넘김으로써 실행 중인 코드의 순서를 바꾼다. Swift는 다섯 가지 제어 전송 구문을 갖고 있다.

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

Break

break 구문은 전체적인 제어 흐름 상태의 실행을 즉시 종료한다. 일반적으로 switch문의 실행을 멈추고 싶거나 반복문을 보다 일찍 끝내고 싶을 경우 사용한다.


반복문에서의 break

반복문 안에서 break 구문을 사용했을 때, 반복문의 실행은 즉시 종료되고 반복문 블록 바깥으로 제어를 넘긴다. 현재 순회 이전, 이후 모두 실행되지 않는다.

switch문 안에서의 break

switch문 안에서 사용한다면 switch문을 종료하고 제어를 블록 바깥으로 넘긴다.

하나 또는 그 이상의 케이스를 무시하고자 할 때 종종 사용된다. switch문은 빈 케이스를 허용하지 않기 때문이다. 케이스 구문의 breakswitch문의 실행을 즉시 종료한다.

주석만 있는 케이스는 컴파일 에러를 발생시킨다. 케이스를 무시하고 싶다면 break를 사용해야 한다.

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

Fallthrough

Swift에서는 switch문이 매치되는 케이스의 아래로 지나가지 않는다. 처음으로 매치되는 케이스가 완료되는 대로 switch문이 끝나는 것이다. break가 없다면 매치된 이후의 모든 케이스를 실행하는 C와는 상반된다. fallthrough 키워드는 이후의 case에 대해서도 실행하게 만든다. 자동으로 break가 사용되는 것을 막는 효과를 가져온다.

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

fallthrough 키워드는 케이스 조건을 확인하지 않고 다음 케이스를 실행하게 한다.


라벨 구문

Swift에서는 복잡한 제어문 구조를 만들기 위해 조건문이나 반복문 안에 다른 조건문, 반복문을 중첩할 수 있다. 이러한 제어문들은 그들의 실행을 조기에 끝내기 위해 break문을 사용할 수 있다. 때문에 어떤 제어문을 끝내고 싶은지 명시하는 것이 유용할 때가 있다. continue문을 사용할 때 역시 마찬가지다.

이를 위해 조건문 또는 반복문에 라벨을 붙일 수 있다. 라벨이 붙은 구문의 실행을 끝내기 위해 break문 또는 continue문을 라벨과 함께 사용한다.

라벨 구문은 콜론(:)과 함께 제어문 키워드와 같은 라인에 위치한다.

label name: while condition {
    statements
}

위에서 보았던 게임 예시에서, while문에 라벨을 설정하여 switch문과 함께 사용할 수 있다.

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!")


이른 종료

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문의 닫는 중괄호 이후로 코드 실행이 계속된다. 옵셔널 바인딩을 사용하여 할당받은 상수/변수는 나머지 코드 블록에서 사용 가능하다.

조건을 만족하지 않았다면, else문 안의 코드가 실행된다. return, break, continue, throw와 같은 제어 전송 구문이나 리턴값이 없는 함수/메소드를 사용할 수 있다.

guard문의 사용은 if문과 비교했을 때 가독성이 높다.



사용 가능한 API 확인

Swift에서는 기본으로 특정 플랫폼 (iOS, macOS, tvOS, watchOS)과 특정 버전을 확인하는 구문을 제공해 준다. 이 구문을 활용해 특정 플랫폼과 버전을 사용하는 기기에 대한 처리를 따로 할 수 있다.

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

0개의 댓글