Chapter 04 - 흐름제어구문

zeStars Team·2021년 5월 13일
1

Swift_Study

목록 보기
3/5
post-thumbnail

Published by KIMDEE

0. 시작하며

프로그래밍 과정에서 작성하는 소스코드 : 구문(Statement)

  • 단순 구문

    식, 값 표현, 각종 객체의 선언, 정의 등에 사용되는 구문.

    연산처리 등도 모두 단순 구문에 속함.

    //변수 또는 상수 선언
    let zestars = 4;
    var cat = """
    우리 집 고양이 이름은
    김산입니다.
    매우 귀여워요. 
    """
    
  • 흐름제어 구문

    프로그램 실행 과정에서 실행 흐름을 능동적으로 제어하기 위한 목적.

    순차적으로 실행될 일부 실행과정을 건너뛰거나 되돌아오도록, 또는 반복적으로 실행되도록 흐름을 제어.

스위프트의 흐름 제어 구문은 크게 3가지

  1. 반복문 Loop Statements
  2. 조건문 Conditional Statements
  3. 제어 전달문 Control Transfer Statements

흐름제어 구문에서는 { 코드 블록 }을 지정해야하는 경우가 많다.

1. 반복문 (Loop)

루프 횟수가 정해져 있는지를 기준으로 for, while

for : 횟수에 의한 반복

while : 조건에 의한 반복

for~in

/*
C 스타일의 for 구문은 2.0 버전까지는 사용할 수 있었으나 
3.0 버전 이후부터는 더 이상 지원되지 않으며, 
Swift에서 for 반복문은 for~in뿐. 
*/

for(int i=0;i<5;i++)
	printf("이제 이런 초기값, 조건식, 증감값으로 처리할 수 없다.\n");
for <루프 상수> in <순회 대상> {
	<실행할 구문> // 코드블록 사이에 작성되어야 함 
} 

순회대상으로 사용할 수 있는 데이터 타입

  • 배열 Array
  • 딕셔너리 Dictionary
  • 집합 Set = Collective Types = Container Type
  • 범위 데이터 (범위 연산자 '...' 에 의해 규칙적인 간격으로 나열된 정수들 모음)
  • 문자열 String

순회대상은 주로 순번을 가지는 집단 자료형이나 범위를 가지는 데이터 등이 사용됨.

for~in 구문을 이루는 루프상수는 구문이 반복될 때마다 순회대상이 포함하는 개별아이템을 차례로 넘겨받아 임의로 저장, 실행 블록내에서 사용할 수 있도록 해주는 역할.

루프상수는 루프 구문이 순회할 때마다 자동으로 재선언 되므로 let 키워드를 사용하여 직접 선언할 필요가 없음.

정의된 루프상수는 오직 for~in 구문의 실행블록 내부에서만 사용 가능.

😎 문자열 순회 방법

String 은 단일 객체로 사용되나, 그 안은 Character 타입의 개별 문자들이 모여 이루어진 객체

String 타입 자체는 순회처리를 지원하지 않으므로, characters 속성을 사용해야함.

var lang = "swift"
for char in lang.characters {
	print("\char")
}

[실행결과]
s
w
i
f
t

🤠 루프 상수의 생략 _

순회대상 자체보다 순회대상의 크기만큼 반복하는 것이 목적일 때, 언더바 _ 를 사용하여 루프상수를 생략할 수 있음.

let size = 5
let padChar = "0"
var keyword = "6"

for _ in 1...size {
	keyword = padChar + keyword 
}

print("\(keyword)")
// 실행결과 000006

😇 for~in 구문의 중첩

다중 루프 또는 멀티루프 (정식 명칭이 아님)

while

조건을 만족하는 동안 실행.

while <조건식> {
	<실행할 구문>
}

조건식은 반드시 참이나 거짓을 결과값으로 반환해야 함.

😎 무한 루프

while true {
    ... 
}

/* 
모바일 앱 등 사용자 액션을 기다리는 대기 상태를 유지해야되는 경우, 
이벤트 루프(=무한루프)를 만들어 실행함. 

Event-Driven Programming 
: 순서대로 수행된 후 프로그램이 종료되는 기존 방식의 프로그래밍이 아닌
특정 상황에서 실행할 구문을 작성해두고 이를 사용자 이벤트와 연결하여 프로그래밍하는 방식. 
*/

repeat~while

타 언어의 do-while 구문에 해당.

  • 스위프트 2.0 버전부터 새로 추가되었음.

    기존에 사용되던 do~while 구문이 2.0 버전에서 예외처리 구문으로 변경되고

    그 대신 repeat~while 구문이 제공되기 시작함.

repeat {
	<실행할 구문>
}
while <조건식>

2. 조건문

분기문 (Branch Statements)

프로그램에서 하나 또는 그 이상의 조건값에 따라 특정 구문을 실행하도록 프로그램 흐름을 분기하는 역할.

Swift에서 제공하는 조건문은 3가지
1. if
2. guard
3. switch

if

if 다음의 조건식은 반드시 Bool 타입의 참, 거짓을 판단할 수 있는 형태이어야.

과거 C 스타일에서 참, 거짓 대신에 숫자 0, 1을 사용할 수 있었지만 Swift에서는 허용하지 않음.

(소괄호)를 이용하여 조건식을 감싸주어도 되지만 필수사항은 아님.

// if 구문
if <조건식> {
	<실행할 구문>
}

// if~else

if <조건식> {
	<조건식이 참일 때 실행할 구문>
} else {
	<조건식이 거짓일 때 실행할 구문>
}

// if~else if
if <조건식 1> {
	<조건식 1가 참일 때 실행할 구문>
} else if <조건식 2> {
	<조건식 2가 참일 때 실행할 구문>
} else {
	<위 조건을 모두 만족하지 않았을 때 실행할 구문>
}

guard

if문과 마찬가지로 주어진 표현식의 결과가 참, 거짓이냐에 따라 구문의 실행 여부를 결정짓는 방식.

guard 구문은 else 블록이 필수. 또한 참일 때 실행되는 블록이 없다.

guard <조건식 또는 표현식> else {
	<조건식 또는 표현식 결과가 false일 때 실행될 코드>
}

후속 코드들이 실행되기 전 특정조건을 만족하는지 확인하는 용도.

특정조건을 만족하지 않은 채 후속코드를 실행하면 심각한 오류가 발생하는 경우에 전체구문을 조기종료(Early Exit) 하기 위한 목적으로 사용됨.

때문에 guard 구문의 else 블록에서는 이후의 코드진행을 막아주는 구문이 반드시 포함되어야.
(e.g. return, break, continue, throw, 또는 반환값이 없는 함수나 메서드를 호출할 수 있다. fatalError(_:file:line:)

// Divide by Zero 오류를 피하기 위해 guard 구문을 이용한 함수 선언

func divide(base: Int) {

	guard base != 0 else {
		print("연산할 수 없습니다.")
		return
	}
	let result = 100 / base
	print(result)
}

guard 구문은 본래 실행 흐름을 종료하기 위한 목적 : 코드를 중첩해서 사용하지 않아도 된다는 장점이 있음.

→ 즉 guard 구문을 많이 사용해도 코드의 깊이가 깊어지지 않음.

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

#available

OS 버전별로 구문을 나누어 작성해야 할 때 사용.

e.g. 특정 API (애플 개발자용 API 문서를 참고해야)

Swift 2 버전부터 지원(built-in support)하기 시작한 구문.

이전에는 OS 버전을 추출하는 API를 직접 호출하여, OS 버전에 대한 값을 얻고 조건문에서 비교처리하는 방식을 썼어야.

//Swift 2 이전 버전까지의 방식
import UIKit

if(UIDevice.current().systemVersion.hasPrefix("9")) {

} else if(UIDevice.current().systemVersion.hasPrefix("8")) {

} else {

}

#available 구문을 사용하여 더 간결하게 구분이 가능

if #available( <플랫폼이름 버전>, <...>, <*> ){
	<해당버전에서 사용할 수 있는 API 구문>
} else {
	<API를 사용할 수 없는 환경에 대한 처리>
}

호출연산자( ) 를 통해 플랫폼 이름과 버전 등 인자값을 입력할 수 있음.

플랫폼 이름과 버전 사이는 공백으로 구분.

OSX 10.10

인자값은 가변인자로 정의되어 있으므로 입력갯수의 제한이 없음.

쉼표로만 구분하여 플랫폼 이름과 OS 버전을 나열하기만 하면 됨.

플랫폼과 버전은 상수로 인식되므로 문자열 처리를 위한 따옴표는 필요없음.

나열이 끝나면 마지막은 * 로 마감하여 인자값 인력이 모두 끝났음을 선언

if #available(iOS 9, OSX 10.11, watchOS 1, *) {
	
}else {
	// API를 사용하지 못했을 때 실패 처리 
}

🤓 #available 구문을 사용할 플랫폼

  • 아이폰, 아이패드 등 터치기반 스마트기기에 사용되는 iOS
  • 맥 용 OSX
  • 애플시계 watchOS
  • 애플TV tvOS

참고

Attributes - The Swift Programming Language (Swift 5.4)

메이저 버전( iOS 8, macOS 10.10 등) 을 명기할 뿐 아니라 마이너 버전까지도 지정할 수 있다. (iOS 11.2.5, macOS 10.13.3

switch

입력받은 값을 패턴을 비교하고 결과를 바탕으로 실행블록을 결정하는 조건문.

switch <비교대상> {

	case <비교패턴1> :
		<비교패턴 1이 일치했을 때 실행되는 구문>
	case <비교패턴2>, <비교패턴3> :
		<비교패턴 2 또는 3이 일치했을 때 실행되는 구문>
	default :
		<어느 비교패턴과도 일치하지 않았을 때 실행되는 구문>
}

Swift에서는 전통적인 언어 방식과 다르게 각 case 키워드 블록마다 break 구문을 생략할 수 있다.

즉, Swift에서는 암시적인 Fallthrough가 적용되지 않음.

때문의 각각의 case는 반드시 실행할 수 있는 구문이 포함되어야함. (비어있어서는 X)

명시적으로 fallthrough 구문을 사용하여, 같은 결과를 얻을 수 있음.

fallthrough에 의해 실행흐름을 전달받은 case 블록은 비교패턴의 일치여부와 상관없이 작성된 구문을 실행한 후 switch 구문을 종료.

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

하나 이상의 비교패턴을 연이어 작성할 수 있음.

하나의 case 키워드로 비교패턴을 묶어 표현하여, 코드를 보다 간결하게 만드는 데 효과적.

default 구문을 생략할 경우 완전하지 않은 구문으로 간주하여 오류 발생

단, 모든 패턴을 매칭시킬 수 있는 구문이 존재하는 경우에 한해 default구문을 생략할 수 있음.

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

🤠 튜플 Tuple

튜플 내부 아이템이 비교 대상과 부분적으로 일치할 경우, case 구문의 비교패턴 전체가 일치하는 것으로 간주. 일치하지 않는 나머지 부분을 상수 또는 변수화하여 사용할 수 있음.

var value = (2, 3)

switch value {
	case let (x, 3) :
		printf("")
	case let (3, y) :
		print("")
	case let (x, y) :
		print("")
}

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

3. 제어전달문

코드의 한 부분에서 다른 부분으로 제어 흐름을 전달하여 코드가 실행되는 순서를 변경해주는 구문.

스위프트에서 사용되는 4가지의 제어 전달문

  1. break
  2. continue
  3. fallthrough
  4. return

break

반복 실행중인 루프나 switch 구문에서의 실행흐름 등, 조건식 결과와 상관없이 즉각적으로 종료하는 데에 사용됨.

  • switch 구문 : 개별 case 블록에 사용되어 전체 switch 구문의 실행을 종료하는 역할
  • 반복문 : 조건식이 false를 반환하기 전에 미리 반복문을 종료하는 역할
for row in 0...5 {
	if row > 2 {
		break
	}
	print("\(row) was executed.")
}

continue

구문 아래에 있는 실행구문을 건너뛰고 다음 반복을 시작하는 역할.

break 구문이 반복문을 완전히 종료하는 것과 달리 continue구문은 반복문의 조건을 다시 평가하고 그 결과에 따라 다음 반복을 실행.

for row in 0...5 {
	if row > 2 {
		continue
	}
	print("\(row) was executed.")
}

구문 레이블과 break, continue

스위프트에서는 루프나 조건문을 다른 루프나 조건문 안에 중첩(nest)해서 좀 더 복잡한 흐름 구조로 짤 수 있다.

그러나, 반복문과 조건문 모두 break 구문을 실행을 조기종료하는 용도로 사용한다.

중첩된 구문 내에서 continue와 break 구문을 사용하려면, 사용된 구문이 어떤 구문을 멈추거나 다시 시작하게 할 지 명확하지 않다. 때문에 명시적으로 구문을 명기하면 어떤 구문을 break 할 지 판단하기 좋아진다.

🤠 구문 레이블 (Statement Label)

Swift에서는 반복문이나 조건문 등 특정구문에 레이블을 붙여 기억할 수 있도록 하고, break나 continue 구문이 사용될 때 레이블을 명시해주어 개발자가 원하는 구문위치에 정확한 흐름제어가 적용될 수 있도록 하는 문법을 제공함

<레이블 이름> : while <조건식> {
	<실행할 구문>
}

break <레이블 이름>
continue <레이블 이름> 

아래 예시는 break 구문과 continue 구문을 사용하고, 그리고 구문레이블을 곁들인..., 뱀과 사다리 Snakes and Ladders 게임이다.

여기에 한가지 규칙을 더 추가하였다.

  • 승리 조건 : 정확히 25번 사각형에 도착해야한다.

만약 주사위를 굴린 숫자가, 25번 사각형을 넘어간다면, 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 루프와 switch 구문을 이용해 게임 로직을 확장. while 루프는 gameLoop 라는 구문레이블이 붙음 = 메인 게임 루프.

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

만약 break 구문에서 gameLoop 레이블을 사용하지 않았으면, 해당 break 구문은 while 구문이 아니라 switch 구문을 종료시켰을 것.

레이블을 이용하여, 어떤 제어구문을 종료시킬지 명확하게 명시할 수 있음.

continue 구문을 사용할 때는 반대로 레이블을 사용하는 것이 반드시 필요한 부분은 아닌데, 그 이유는 위 코드에서는 하나의 루프만 있기 때문에 무엇을 continue할 지가 명징함. 그러나 레이블을 같이 쓴다고 해서 문제가 생기는 것은 아니므로, 코드의 논리를 명확하게 하고 코드를 이해하기 편하게 하도록 break 구문과 같이 continue에도 레이블을 함께 사용해주는 것을 권장함.

참고

Swift guard statement

Apple Developer Documentation

Control Flow - The Swift Programming Language (Swift 5.4)

profile
zeStars 개발 블로그입니다

0개의 댓글