본문 바로가기

Swift/Swift Language

Swift 언어 가이드 - 기초

기초

스위프트는 iOS, macOS, watchOS 그리고 tvOS 앱 개발을 위한 새로운 프로그래밍 언어입니다. 스위프트의 많은 부분들이 C, Objective-C와 비슷합니다.

스위프트는 C와 Objective-C의 모든 근본적인 요소인 타입을 포함합니다. 예를 들어, 정수형을 표현하는Int , 부동 소수점 값을 표현하는 Double, 불리언 값을 위한 Bool, 그리고 텍스트 데이터를 표현하기 위한 String이 있습니다. 스위프트는 컬렉션 타입 에 서술된 것과 같이 배열, 집합, 그리고 딕셔너리라는 기본적인 세 가지 컬렉션 타입을 제공합니다.

C언어와 유사하게, 스위프트는 값을 저장하고 참조하기 위해 변수를 사용합니다. 이 변수는 이름에 의해 식별이 됩니다. 스위프트는 또한 값이 변하지 않는 확장된 변수를 만들 수 있습니다. 이 변수를 상수라고 하며 C 언어보다 더 강력합니다. 스위프트의 상수는 코드를 안전하고 깔끔하게 만들 수 있도록 사용됩니다. 익숙한 타입들과 더불어, 스위프트는 Objective-C이 가지지 않는 진보된 타입들을 도입했습니다. 튜플을 그 예로 들 수 있습니다. 튜플은 하나로 묶여진 값들을 의미합니다. 여러 개의 값들을 하나의 함수 반환 값으로 만들고 싶을 때, 하나로 묶여진 값인 튜플을 사용할 수도 있습니다.

스위프트는 또한 옵셔널 타입이라는 것을 제공합니다. 옵셔널은 값이 없는 상태를 다룰 수 있습니다. 옵셔널은 다음과 같이 ??“하나의 값이 존재 할 때, 이것이 x와 같다.” 혹은 “ 값이 존재하지 않는다.” 마치 Objective-C가 nil을 포인터로 다룰 때와 유사합니다. 하지만 스위프트의 옵셔널은 클래스에 국한되어 있지 않고 모든 타입에 대응됩니다. 옵셔널은 Objective-C의 nil 포인터와 비교하여 안전하고 expressive합니다. 스위프트는 타입에 안전한 언어입니다.즉, 스위프트는 당신이 값들의 타입들에 대해 명확하게 하도록 도와줍니다. 만약, 당신의 코드가 String타입이 필요로 한다면, 타입 안정성은 당신이 IntString으로 착각하여 사용하는 것을 방지해줍니다. 또한, 논옵셔널 스트링이 필요한 코드에 옵셔널 스트링을 사용하지 않도록 도와줍니다. 타입 안정성은 당신이 개발 단계에서 에러를 최대한 빠르게 잡아내고 고칠 수 있도록 합니다.

상수와 변수

상수와 변수는 특정한 타입을 가진 값과 이름을 연관 짓습니다. 상수는 한번 값이 정해지면 바꿀 수 없습니다. 반면, 변수는 이후에 다른 값으로 변경할 수 있습니다.

상수와 변수 선언하기

상수와 변수를 사용하려면 그 이전에 선언되어야 합니다. let키워드는 상수를 선언할 때 사용되며 변수는 var키워드로 사용합니다. 다음의 예제를 통해 어떻게 변수와 상수가 어떻게 값을 추적하는지 살펴보겠습니다.

let maximumNumberOfLoginAttempts = 10 // 최대 로그인 시도 가능 횟수
var currentLoginAttempt = 0 // 최근 로그인 시도 횟수

이 코드는 다음과 같이 읽을 수 있습니다. :
“새로운 상수값을 maximumNumberOfLoginAttempts라고 명명하고 10이라는 값을 부여하자. 그리고 나서, currentLoginAttempt라는 변수를 선언하는데 이 값은 최초로 0을 가진다.” 최대로 로그인을 시도할 수 있는 횟수는 상수로 정했습니다. 왜냐하면 이 값은 코드를 통틀어 바뀌지 않기 때문입니다. 최근 로그인 시도 횟수는 변수로 지정했습니다. 왜냐하면 로그인이 실패할 때마다 이 값은 계속 증가해야하기 때문이죠.

여러 개의 상수와 변수를 한번에 쓰는 것도 가능합니다.

var x = 0.0, y = 0.0, z = 0.0

참고
저장된 값이 바뀌지 않는다면 항상 let 키워드를 이용해 상수로 그 값을 선언하십시오. 변수는 오로지 값이 바뀔 때만 사용하도록 하십시오.

타입 표기

당신은 또한 변수 혹은 상수에타입을 표기 할 수 있습니다. 타입 표기를 표기하는 방법은 변수나 상수뒤에 콜론을 적는 것입니다. 콜론 뒤에 공백과 타입명이 위치합니다.

이 예제는 welcomMessage라는 변수에 String 타입을 표기하였습니다.

var welcomeMessage: String

콜론은 “이 변수는 다음의 타입을 가진다”라고 해석할 수 있습니다. 따라서 이 코드를 해석해 보면 다음과 같습니다 : “welcomMessage라는 이름의 상수를 선언하되, 타입을 스트링으로 정하자.”

이 변수는 아래와 같이 새로운 값을 저장할 수 있습니다.

welcomeMessage = "Hello"

같은 타입을 가진 여러 개의 변수들을 하나의 줄에 선언하고 싶다면 어떻게 할까요? 가장 마지막의 변수에 타입 표기를 해주면 됩니다.

var red, green, blue: Double

참고
당신이 실제로 코드를 작성할 때 타입 표기를 작성하는 경우는 드물 것입니다. 만약 변수나 상수가 선언되고 동시에 최초의 값을 저장할 때 스위프트는 그 변수(혹은 상수)의 타입을 추론하여 지정해줍니다. 위의 welcomeMessage 예제에서는, 최초의 어떠한 값도 지정되지 않았습니다. 따라서 welcomMessage 변수는 최초의 값을 통해 타입이 추론된 것이 아니라, 타입이 지정된 것입니다.

상수와 변수에 이름 짓기

변수와 상수에 이름을 지정할 때 유니코드 문자를 포함한 거의 모든 문자를 사용할 수 있습니다.

let n = 3.14159
let 안녕 = "헬로 월드"
let 🐶🐮 = "개소"

상수와 변수의 이름은 공백을 가질 수 없습니다. 또한 수학 표기와 화살표, 개인이 사용하는 유니코드, 특수문자들을 사용할 수 없습니다. 숫자로 변수와 상수의 이름을 시작하는 것은 불가능하지만, 숫자가 시작부분에 없는 것은 모두 허용합니다. 만약 당신이 변수와 상수의 타입을 한번 지정하면 이후에 똑같은 이름으로 선언할 수 없습니다. 그 변수에 다른 타입의 값을 저장하는 것은 허용하지 않습니다. 상수에서 변수로 바꾸거나 변수에서 상수로 바꾸는 것은 불가능합니다.

참고
스위프트가 이미 가지고 있는 키워드(예약어라고 합니다.)와 똑같은 이름으로 상수와 변수에 이름을 정하고 싶을 때가 있습니다. 이 때는 백틱(`)을 이용하세요. 하지만 예약어를 사용하는 것은 최대한 피하세요.

당신은 이미 존재하는 변수에 타입이 호환된다면 다른 값을 넣을 수 있습니다. 이 예제에서는 friendlyWelcome이라는 변수가 “Hello!”에서 “Bonjour!”로 바뀌는 것을 보여줍니다.

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"

변수와 다르게, 상수의 값은 한번 정해지면 바꿀 수 없습니다. 만약 바꾸려고 시도한다면 코드가 컴파일 될 때 에러를 발생합니다.

let languageName = "Swift"
languageName = "Swift++" // 컴파일 타임 에러 !!!

상수와 변수 출력해보기

당신은 이제 상수와 변수의 값을 print(_:separator:terminator:)함수를 이용해 출력할 수 있습니다.

print(friendlyWelcome)
// "Bonjour!" 출력

print(_:separator:terminator:)함수는 전역 함수입니다. 이 함수는 하나 이상의 값을 적절한 값으로 출력합니다. Xcodeprint(_:separator:terminator:)함수가 호출되면 Xcode의 “콘솔”창에서 출력합니다. separator와 terminator 파라미터는 기본 값을 가지고 있습니다. 따라서 당신이 이 함수를 호출 할 때 생략해도 무방합니다. 출력된 값들이 끊어지지 않도록 하려면, terminator에 빈 문자열 값을 넣어줍니다. 예를 들어, print(someValue, terminator: “”)처럼 사용합니다. 더 자세한 내용은 기본 파라미터 값 을 참조하세요.

스위프트는 문자열 보간을 사용합니다. 매우 긴 문자열에서 상수나 변수의 이름을 그대로 포함할 때 사용하는 것이 문자열 보간입니다. 변수 혹은 상수의 이름을 소괄호로 감싸고 역슬래시 값을 맨앞에 둡니다.

print("friendlyWelcome의 현재 값은? \(friendlyWelcome)")
// friendlyWelcome의 현재 값은? Bonjour!

참고
문자열 보간의 모든 옵션에 대해서는 다음 문자열 보간 을 참고하세요.

주석

실행되지 않는 문자를 코드안에 포함시키고 싶다면 주석을 사용하세요. 주석은 스위프트 컴파일러에 의해 무시됩니다.

스위프트의 주석은 C의 주석과 매우 비슷합니다. 한 줄 주석은 두개의 슬래시(//)로 시작합니다.

// 이것은 주석입니다.

두 줄 이상의 주석은 슬래시와 아스터리스크로 시작합니다. 또한 주석의 마지막은 아스터리스크와 슬래시여야 합니다.

/* 이것 또한 주석입니다.
 하지만 두 줄 이상이 적혀 있습니다. */

C 언어의 2줄 이상의 주석을 작성할 때와는 다르게 스위프트는 주석안에 주석을 중첩할 수 있습니다. 중첩 주석을 사용하려면 2줄 이상의 주석을 사용하는 것처럼 시작하고 두 번째 주석 또한 같은 방식으로 사용합니다. 주석을 닫을 때 내포된 두 번째 주석이 닫히고 첫 번째 바깥의 주석이 닫힙니다.

/* 이것은 첫 번째 주석입니다.
  /* 이것은 두 번째 중첩된 주석입니다. */
    이것은 첫 번째 주석의 끝입니다. */

중첩된 2줄 이상의 주석은 좀 더 큰 코드가 이미 주석이 처리되어 있지만 이 코드를 빠르게 주석 처리하고 싶을 때 사용합니다.

세미콜론

다른 언어들과 다르게, 스위프트는 문장의 끝에 세미콜론(;)을 요구하지 않습니다. 하지만 한 줄에 여러 개의 문(statement)을 작성한다면 세미콜론이 필요합니다.

let cat = "🐱";print(cat)
// Prints “🐱"

정수(Integers)

정수(Integers)는 42와 -23과 같은 정수(whole number) 값을 의미합니다. 정수는 부호가 있거나 부호가 존재하지 않을 수 있습니다.
스위프트는 부호가 있는 부호가 존재하지 않는 정수를 8, 16, 32 그리고 64 비트 형태로 제공합니다. 이 정수들은 C의 네이밍 컨벤션(Naming convention)을 따릅니다. 8비트의 부호가 없는 정수인 UInt8과 32비트의 정수가 있는 정수형 타입인 Int32처럼 스위프트는 모든 정수형 타입의 이름이 대문자로 시작합니다.

정수형 범위

정수형의 최소값과 최대값에 접근하는 방법은 최소, 최대 값 속성을 이용하는 것입니다.

let minValue = UInt8.min // minValue는 0과 같습니다.
let maxValue = UInt8.max // maxValue는 255와 같습니다.

위의 속성 들의 값들은 appropriate-sized 숫자 형이라고 합니다. 따라서, UInt8과 같은 타입은 같은 타입의 다른 값들과 함께 사용할 수 있습니다.

Int

대부분의 경우, 여러분들은 정수의 정확한 사이즈를 생각하여 그것을 사용할 필요가 없습니다. 스위프트가 추가적인 정수형 타입인 Int를 제공합니다. Int는 현재 플랫폼의 워드값과 동일합니다. 예를 들어,

  • 32비트 플랫폼의 경우 Int는 Int32와 같습니다.
  • 64비트 플랫폼의 경우 Int는 Int64와 같습니다.
    정수의 정확한 크기를 이용해 코드를 작성하지 않는다면, 항상 Int를 사용하세요. 이 방식이 일관성과 호환성에 도움이 됩니다. 32비트의 플랫폼이더라도 Int는 -2,147,483,648과 2,147,483,647 사이의 값을 저장할 수 있습니다. 대부분의 정수의 경우 충분한 범위입니다.

UInt

스위프트는 부호가 없는 정수형 타입을 제공합니다. 현재 플랫폼의 워드 사이즈와 같은 값을 가집니다. 예를 들어,

  • 32비트 플랫폼의 경우 UInt32와 UInt는 같은 값을 가집니다.

  • 64비트 플랫폼의 경우, UInt64와 UInt는 같은 값을 가집니다.

참고
플랫폼의 워드 크기와 부호 없는 정수를 같은 크기로 사용하려면 항상 UInt를 사용하도록 하세요. 사용하지 않을 경우, Int를 사용하세요. 그 값들이 심지어 음수가 아닌 것을 알아도 Int를 사용하세요. 일관되게 정수형 값을 Int로 사용하는 것은 상호 호환성에 도움이 됩니다. 이를 통해, 다른 숫자 타입으로 바꾸는 번거로움과 정수형 타입 추론을 맞추는 것을 피할 수 있습니다. 자세한 사항은 Type Safety와 Type Inference에 있습니다.

부동 소수점 숫자(Floating-Point)

부동 소수점 숫자(Floating-point numbers)는 분수로 표현되는 숫자를 말합니다. 예를 들어, 3.14159, 0.1 그리고 -237.15와 같은 값을 의미합니다. 부동 소수점 타입은 정수형보다 좀 더 넓은 범위의 값을 표현할 때 사용합니다. 스위프트는 2개의 부호가 있는 부동 소수점 숫자를 제공합니다.

  • Double은 64비트 부동 소수점 숫자입니다.
  • Float는 32비트 부동 소수점 숫자입니다.

참고
Double은 최소 15개의 십진수를 가지며 Float의 정밀도는 6개의 십진수를 가집니다. 적절한 부동소수점 타입을 사용하는 것은 여러분이 코드에서 사용할 값의 범위와 특성에 따라 달라집니다. 두 개의 타입이 모두 사용될 수 있는 상황에서는 Double을 사용하세요.

타입 안전성과 타입 추론

스위프트는 정적 타입 언어입니다. 정적 타입 언어는 사용자로 하여금 코드를 작성할 때 값의 타입을 명확하게 할 것을 요구합니다. 만약 코드 상에서 String 타입이 필요하다면 실수로 인해 Int형이 이것을 대체할 수 없습니다. 스위프트는 정적 타입언어 이므로 컴파일 시점에 타입 체크가 이루어집니다. 타입이 일치하지 않는 것을 에러로 인식합니다. 이러한 기능이 개발 시점에 최대한 빠르게 에러를 찾고 고칠 수 있도록 도와줍니다.

타입 체킹은 다른 타입으로 코드를 작성할 때 에러가 발생하는 것을 막아줍니다. 하지만 상수나 변수를 선언할 때 항상 타입을 지정해야한다는 것은 아닙니다. 만약 어떠한 값의 타입을 작성하지 않는다면 스위프트가 적절한 타입으로 맞추어 주기 위해 타입을 추론합니다. 타입 추론은 컴파일러로 하여금 자동으로 특정 표현의 타입을 추론하도록 합니다. 타입 추론으로 스위프트는 C 나 Objective-C에 비해 좀 더 적은 수의 타입 선언을 가능하도록 합니다.
타입 추론은 상수나 변수에 초기값과 함께 선언 시에 매우 유용합니다. 보통 상수나 변수에 리터럴 값을 부여할 때 타입 추론이 이루어집니다.
예를 들어 알아보겠습니다. 만약, 리터럴 값으로 42를 새로운 상수에 선언하고 타입을 적지 않도록 하겠습니다. 스위프트는 이 상수를 Int로 추론합니다. 왜냐하면 이 숫자가 정수처럼 보이기 때문입니다.

let meaningOfLife = 42
// meaningOfLife Int타입으로 추론됨

이와 비슷하게 부동 소수점 리터럴에 타입을 적지 않는다면, 스위프트는 이것을 Double로 인식합니다.

let pi = 3.14159
// pi는 Double타입으로 추론됨

스위프트는 부동소수점 숫자의 타입을 추론할 때 항상 Float보다 Double로 선언합니다. 만약 정수와 부동소수점 리터럴을 하나의 표현식으로 작성하면 Double이 추론됩니다.

let anotherPi = 3 + 0.14159
// anotherPi는 Double타입으로 추론됨

숫자 리터럴(Numeric Literals)

정수형 리터럴은 다음과 같이 작성됩니다.

  • 십진수는 접두사가 없습니다.
  • 이진수는 0b가 접두사입니다.
  • 8진수는 0o가 접두사입니다.
  • 16진수는 0x가 접두사입니다.

아래의 모든 정수형 리터럴은 십진수로 17을 갖습니다.

let decimalInteger = 17
let binaryInteger = 0b10001 // 2진수로 17
let octalInteger = 0o21 // 8진수로 17
let haxadecimalInteger = 0x11 // 16진수로 17

부동 소수점 리터럴은 접두사가 없는 10진수가 될 수 있습니다. 또는 16진수를 0x를 접두사로 붙여 가능합니다. 소수점의 양쪽은 십진수 혹은 16진수여야 합니다. 10진수 float는 추가적인 지수부를 가질 수 있습니다. 이것은 대문자나 소문자 e로 표현합니다. 16진수 float는 반드시 지수부를 가지며 대문자나 소문자 p로 표현합니다.

  • 1.25e2는 1.25 x 102 또는 125.0을 의미
  • 1.25e-2는 1.25 x 10-2 또는 0.0125를 의미
    For hexadecimal numbers with an exponent of exp, the base number is multiplied by 2exp:
  • 0xFp2 는 15 x 22 또는 60.0 을 의미
  • 0xFp-2는 15 x 2-2 또는 3.75를 의미

아래의 부동 소수점 리터럴은 모두 10진수로 12.1875입니다.

  • let decimalDouble = 12.1875
  • let exponentDouble = 1.21875e1
  • let hexadecimalDouble = 0xC.3p0

숫자형 리털은 읽기 쉽도록 부가적인 형태를 가집니다. 부동소수점 숫자와 정수형은 모두 추가적인 0으로 채워넣을 수 있습니다. 또한 가독성을 위해 언더스코어를 사용할 수도 있습니다. 이러한 타입 포매팅이 실제 값에 영향을 주지 않습니다.

  • let paddedDouble = 000123.456
  • let oneMillion = 1_000_000
  • let justOverOneMillion = 1_000_000.000_000_1

숫자 타입 변환

Int 타입은 일반적인 정수형 상수, 변수에 맞습니다. 비록 당신이 사용하려는 값이 음수값이 아니더라도 Int를 사용하세요. 기본 정수형 타입을 모든 경우에 사용하는 것은 정수형 변수와 상수를 즉시 호환이 가능하다는 것을 의미합니다.

다른 정수형 타입은 오직 특정한 업무가 필요할 때만 사용하세요. 성능, 메모리 사용량 또는 필수적인 최적화를 위한 목적이 그 이유가 될 수 있습니다. 이 경우의 명시적인 크기의 타입을 사용하는 것은 오버플로를 방지할 수 있습니다.

정수형 변환

정수에 저장되는 숫자의 범위는 각기 다른 숫자 타입에 따라 다릅니다. Int8의 상수나 변수는 -128부터 127까지의 값을 저장할 수 있습니다. 반면 UInt8의 상수나 변수는 0부터 255까지의 숫자를 저장할 수 있습니다. 어떠한 정수형 타입의 범위에도 들어가지 않는 숫자는 컴파일 시점에 에러를 발생합니다.

let cannotBeNegative:UInt8 = 1
// UInt8은 음수값을 저장할 수 없음
let tooBig: Int8 = Int8.max + 1
// Int8은 Int8의 최대값보다 더 큰 값을 저장할 수 없음
//// 따라서 에러를 발생

숫자형 타입은 각기 다른 값의 범위를 저장할 수 있기 때문에 상황에 맞추어 올바른 숫자형 변환을 해야합니다. 올바르게 숫자형 변환을 하는 것은 숨겨진 변환 에러를 방지하고 타입 변환의도를 명시적으로 표현하는데 도움을 줍니다.

특정한 숫자 타입을 다른 숫자 타입으로 바꾸려면, 값이 존재하는 원하는 타입의 새로운 숫자를 초기화하세요. 아래의 예제는 twoThousand 상수가 UInt16 타입으로 선언되어 있습니다. 반면, one이라는 상수는 UInt8으로 선언되어 있습니다. 이 두 상수는 같은 타입이 아니므로 직접적으로 함께 더할 수 없습니다. 이 예제에서는 UInt16인 one 을 초기화하여 UInt16인 twoThousand와 더하게 됩니다.

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

덧셈 연산의 양쪽은 UInt16 타입이 되어 덧셈이 허용됩니다. 결과로 나오는 twoThousandAndOne상수는 UInt16으로 추론됩니다. 왜냐하면 UInt16의 값을 서로 더했기 때문입니다.

어떤타입명(초기화할값)으로 코드를 작성하면 스위프트가 초기화값을 해당 타입으로 선언해줍니다. 내부적으로는 UInt16는 UInt8의 값을 받아서 초기화하는 기능이 있습니다. 어떠한 타입이든 변환할 수 있다는 뜻은 아닙니다. 하지만 UInt16이 초기화가능한 타입이여야 합니다. 초기화 기능이 새로운 타입을 제공하기 위해 타입을 확장하는 것은 Extensions을 참고하세요.

정수와 부동소수점 연산 변환

정수에서 부동소수점 타입으로의 변환은 반드시 명시적으로 이루어집니다.

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 는 3.14159이며 Double로 추론됨

여기서 3개의 상수는 타입이 Double인 새로운 값으로 생성되기 위해 사용되었습니다. 따라서 덧셈연산의 두 항이 같은 타입으로 되어있습니다. 이러한 변환이 없으며 덧셈은 허용되지 않고 에러를 발생합니다.

부동소수점에서 정수형으로 변환하는 것은 암시적으로 이루어질 수 있습니다. 정수형 타입은 Double 혹은 Float로 초기화할 수 있습니다.

let integerPi = Int(pi)
// integerPi는 3과 같으며 Int로 추론되었습니다.

부동소수점 값은 항상 새로운 정수형으로 변환 될 때 소수점이하는 버리게 됩니다. 즉, 4.75는 4가 되며 -3.9는 -3이 됩니다.

참고
숫자형 상수와 변수를 조합하는 규칙은 숫자형 리터럴 규칙과는 다릅니다. 리터럴 값이 3이라면 이 값은 곧바로 0.14159라는 리터럴 값과 더해질 수 있습니다. 왜냐하면 리터럴 숫자란 명시적인 타입이 존재하지 않은 상태이기 때문입니다. 리터럴 타입은 오직 컴파일 시점에만 타입이 추론됩니다.

타입 별칭

타입 별칭은 기존 타입 대신 다르게 사용할 수 있는 것을 의미합니다. typealias 키워드를 이용해 타입 별칭을 만들 수 있습니다. 타입 별칭은 여러분이 기존 타입을 문맥적으로 올바른 의미로 대신할 때 사용하는 것이 좋습니다. 예를 들어 외부 자료로부터 구체적인 크기의 데이터를 다룰 때가 그 경우입니다.

typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 값은 0임

예제에서 AudioSample은 UInt16 대신에 사용되었습니다. AudioSample.min은 실제로 UInt16.min을 의미합니다. 따라서, maxAmplitudeFound변수는 초기에 0으로 제공됩니다.

불리언 타입

스위프트는 Boolean이라고하는 기본 타입을 가지고 있습니다. Boolean 값은 참이냐 거짓이냐는 논리를 의미합니다. 스위프트는 2가지 불리언 상수 값인 true와 false를 제공합니다.

let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrange의 타입과 turnipsAreDelicious는 Boolean 리터럴 값으로 초기화 되었으므로 불리언으로 추론되었습니다. Int와 Double과 더불어 상수나 변수를 Bool로 선언할 필요가 없습니다. 단지, true 혹은 false 값을 생성시에만 지정해주면 됩니다. 타입 추론은 스위프트 코드를 가독성있고 간결하도록 도와줍니다. Boolean 값``은 특히 if와 같은 조건문에 사용될 때 유용합니다.

if turnipsAreDelicious {
  print("turnips는 맛있다")
} else {
  print("turnips는 끔찍해")
}
// print "turnips는 끔찍해"

if와 같은 조건절은 흐름 제어에서 다룹니다.

스위프트의 타입 안정성은 Boolean이 아닌 값이 불리언 타입으로 교체되는 것을 막아줍니다. 다음 예제는 컴파일 시점에 에러를 발생합니다.

let i = 1
if i {
  // 이 예제는 컴파일 시점에 에러를 발생
}

let i = 1
if i == 1 {
  // 이 방식은 컴파일이 성공함
}

i==1의 결과는 불리언 타입입니다. 두 번째 예제는 타입 체크에 성공하게 됩니다. i==1과 같은 비교문은 기본 연산에서 다룹니다..

스위프트의 다른 타입 안정성 예제와 같이, 타입 안정성은 에러를 방지하고 특정한 코드의 부분의 의도를 명확하게 해줍니다.

튜플 Tuples

튜플은 2개 이상의 값을 하나의 값으로 묶어줍니다. 튜플에 들어가는 값은 어떠한 타입도 올 수 있지만 서로 같은 타입일 필요가 없습니다.

예제에서는 (404, "Not Found")라는 튜플이 있습니다. 이 튜플은 HTTP 상태 코드를 설명합니다. HTTP 상태 코드는 웹 페이지에 접속하여 웹 서버에 의해 전달 받는 특별한 값입니다. 404 Not Found 상태코드는 웹페이지를 요청했지만 아무것도 전달받지 못했을 때 받는 값입니다.

let http404Error = (404, "Not Found")
// http404Error는 (Int, String)의 타입으로 이루어져 있고 (404, "Not Found")와 같음

(404, "Not Found") 튜플은 Int와 String을 함께 묶어서 HTTP 상태코드를 두 개의 분리된 값으로 제공하기 위해 사용됩니다.(숫자와 사람이 읽기 쉬운 문자로 이루어짐). 이것을 "(Int, String)타입으로 이루어진 튜플이다"라고 표현할 수 있습니다.

모든 순서의 타입으로 튜플을 생성할 수 있습니다. 그리고 튜플은 여러분이 원하는 만큼의 다른 타입을 가질 수 있습니다. 예를 들어, (Int, Int, Int)도 가능하며, (String, Bool)로도 구성할 수 있습니다. 또한 튜플이 가진 값들의 순서를 변경할 수도 있습니다.

튜플의 값들을 개별적인 상수나 변수로도 분해할 수 있습니다.

let (statusCode, statusMessage) = http404Error
print("상태코드는 (statusCode)입니다.")
// "상태코드는 404입니다."가 출력됨
print("상태메시지는 (statusMessage)입니다.")
// "상태메시지는 Not Found입니다."가 출력됨

만약 여러분이 튜플의 일부분만 필요로한다면 언더스코어(_)를 사용하세요.

let (justTheStatusCode, _) = http404Error
print("상태코드는 (justTheStatusCode)입니다. ")
// "상태코드는 404입니다."가 출력됨

튜플을 인덱스 번호로 접근하여 사용할수도 있습니다.

print("상태코드는 (http404Error.0)")
// "상태코드는 404"가 출력됨
print("상태메시지는 (http404Error.1)")
// "상태코드는 Not Found"가 출력됨

튜플이 생성될 때 튜플의 개별적인 원소에 이름을 부여할 수 있습니다.

let http200Status = (statusCode:200, description: "OK")
print("상태코드는 (http200Status.statusCode)입니다.")
// "상태코드는 200입니다."가 출력됨
print("상태메시지는 (http200Status.description)입니다.")
// "상태메시지는 OK"가 출력됨

튜플은 특히 함수의 값을 리턴할 때 유용합니다. 웹 페이지를 호출하는 함수는 대부분 (Int, String) 형태의 튜플을 반환합니다. 이 튜플의 각각이 구별되는 값이고 다른 타입을 가집니다. 좀 더 자세한 내용은 다중 반환 값을 가진 함수(Functions with Multiple Return Values)을 참고하세요.

참고

튜플은 관련이 있는 값들의 간단한 그룹을 만드는데 유용합니다. 하지만 튜플은 복잡한 데이터 구조에는 적합하지 않습니다. 만약 데이터 구조가 좀 더 복잡한 것을 원한다면 클래스(class)나 스트럭쳐(structure)로 생성하세요. 좀 더 자세한 사항은 스트럭쳐와 클래스(Structures and Classes)을 참고하세요.

옵셔널 Optionals

옵셔널은 값이 존재하지 않을 수도 있을 때 사용합니다. 옵셔널은 두 가지의 가능성을 표현합니다. 값이 있거나 값이 전혀 존재하지 않을 수 있습니다. 값이 있을 때는 옵셔널을 해제하면 값에 접근할 수 있습니다.

참고

옵셔널이라는 개념은 C 언어나 Objective-C에 존재하지 않습니다. Objective-C에서 찾을 수 있는 옵셔널에 가장 가까운 개념은 객체를 반환하는 메서드가 객체가 없다면 nil값을 반환하는 것입니다. 여기서 nil이란 "유효한 객체가 존재하지 않다"는 의미입니다. 하지만 Objective-C는 메서드가 객체를 반환하는 경우에만 nil반환이 가능합니다. 기본 C 타입이나 열거형 값, 스트럭쳐에는 동작하지 않는다는 의미입니다. 이러한 타입에 대해서 값이 존재하지 않음을 표현하기 위해 Objective-C의 메서드는 특별한 값(예를 들어 , NSNotFound)을 반환합니다. 이 방법은 메서드를 호출하는 대상이 특별한 값에 대해 테스트하거나 체크할 것을 기억해야 합니다. 스위프트는 어떠한 타입에 대해서도 값의 부재를 표현할 수 있는 옵셔널을 제공합니다. 여기에는 특별한 상수도 필요 없습니다.

여기에 옵셔널이 어떻게 값이 존재하지 않음을 다룰 수 있는지 확인해보겠습니다. 스위프트의 Int 타입은 String 값을 Int값으로 변환하는 기능(initializer)을 가집니다. 하지만 모든 문자열 값이 정수형 값으로 변환할 수는 없습니다. "123"이라는 문자열은 숫자인 123으로 변환할 수 있지만 "hello, world" 문자열은 숫자 값으로 변환할 수 없습니다.

아의 예제는 String을 Int로 변경하는 예제입니다.

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber는 Int? 타입(optional Int)으로 추론됨

possibleNumber라는 String 값을 Int로 변경하는 초기화가 실패할 수도 있으므로 옵셔널 Int를 반환합니다. 옵셔널 Int는 Int?로 표현합니다. 물음표는 이 값이 옵셔널을 가진다는 것을 의미하며 어떤 Int값을 가질수도 있다는 것을 말합니다. 또는, 어떠한 값도 가지지 않음을 의미할 수도 있습니다. (하지만 convertedNumber상수는 Bool이나 String 값은 가질수 없습니다.)

닐 nil

옵셔널 변수를 값이 존재하지 않는 상태로 지정하기 위해 nil이라는 특별한 값을 사용합니다.

var serverResponseCode: Int? = 404
// serverResponseCode는 Int 타입으로 404값을 가짐
serverResponseCode = nil
// serverResponseCode는 이제 어떠한 값도 가지지 않음

NOTE

여러분은 nil을 옵셔널이 아닌 변수나 상수에 사용할 수 없습니다. 만약 특별한 조건에서 상수나 변수에 값이 존재하지 않음을 표현하려면 원하는 타입의 옵셔널 값으로 선언하세요. 값을 지정하지 않고 옵셔널 변수를 선언하면 그 변수는 자동으로 nil로 선언됩니다.

var surveyAnswer: String?
// surveyAnswer는 이제 자동적으로 nil이 됩니다.

NOTE
스위프트의 nil은 Objective-C의 nil과 동일합니다. Objective-C의 nil은 존재하지 않는 객체의 포인터 역할을 합니다. 스위프트에서 nil은 포인터가 아니고 특정한 타입의 값이 없음을 말하는 것입니다. 어떠한 타입이든 옵셔널은 nil로 지정할 수 있습니다.

If 문과 강제 언래핑

옵셔널이 값을 가지고 있는지 확인할 때 옵셔널과 nil을 비교하는 if 조건문을 사용할 수 있습니다. 같음을 비교하는 연산자(==) 또는 같지 않음을 비교하는 연산자 (!=)를 사용하면 비교를 할 수 있습니다. 만약 옵셔널이 값이 존재하면 nil과 같지 않다는 결과가 나올 것입니다.

if convertedNumber != nil {
  print("convertedNumber는 정수 값을 가지고 있다.")
}
// "convertedNumber는 정수 값을 가지고 있다."가 출력됨

여러분이 옵셔널이 값을 가진다고 확신한다면 느낌표 (!)를 사용하여 옵셔널 내부의 값에 접근할 수 있습니다. 이 느낌표를 사용하는 예를 간단히 말하자면, "난 이 옵셔널이 실제 값을 가지고 있다는 것을 확실히 알아."라고 말할 수 있습니다. 이 것을 옵셔널 값의 강제 언래핑이라고 합니다.

if convertedNumber != nil {
  print("convertedNumber는 정수형인 \(convertedNumber!)을 가진다.")
}
// "convertedNumber는 정수형인 123을 가진다."가 출력됨

if 조건문에 대해 자세한 사항은 흐름 제어 (Control Flow)를 참고하세요.

참고
존재하지 않는 옵셔널 값에 접근하기 위해 !를 사용하면 런타임 에러가 발생합니다. 항상 옵셔널의 값이 nil값이 아닌지 확실히 알고 강제 언래핑인 느낌표(!)를 사용하세요.

옵셔널 바인딩

옵셔널이 값을 가지고 있는지 확인하고 싶다면 옵셔널 바인딩을 사용할 수 있습니다. 옵셔널 바인딩은 옵셔널 내부에 값이 있는지 확인하기 위해 if와 while 조건문에서 사용할 수 있습니다. 그리고 그 값을 추출하여 상수나 변수에 대입할 수 있습니다. 만약 if와 while조건문에 대해 자세히 알고 싶다면 흐름 제어 (Control Flow)를 참고하세요.

if 조건문에서 옵셔널 바인딩은 다음과 같이 사용할 수 있습니다 :

if let constantName = someOptional {
  // 수행할 문(statements)
}

강제 언래핑 대신, 옵셔널 바인딩을 이용한 possibleNumber예제를 다음과 같이 사용할 수 있습니다.

if let actualNumber = Int(possibleNumber) {
  print("문자열 \"\(possibleNumber)\"는 정수 값인 \(actualNumber)를 가진다.")
} else {
  print("문자열 \"\(possibleNumber)\"는 정수 값으로 변환할 수 없다.
}
  // "문자열 "123"은 정수 값인 123을 가진다." 가 출력됨

이 코드를 다음과 같이 해석할 수 있습니다. :

"만약 Int(possibleNumber)가 옵셔널 정수를 리턴하여 옵셔널 정수가 실제 값을 가진다면 새로운 상수인 actualNumber에 그 옵셔널 정수의 실제 값을 저장하라."

만약 변환이 성공되면 actualNumber상수는 if 조건문의 첫번째 블록에 사용됩니다. 따라서 !를 사용하여 값에 접근할 필요가 없습니다. 이 예제에서는 actualNumber가 단순히 변환한 결과를 출력하기 위해 사용되었습니다.

상수나 변수를 옵셔널 바인딩에 사용할 수 있습니다.만약 actualNumber의 값을 if조건문의 첫 번째 블록에서 다루길 원한다면 if var actualNumber라고 사용할 수 있습니다. 또한, 옵셔널이 가진 값을 상수가 아닌 변수로 사용할 수 있습니다.

if조건문에 원하는 개수 만큼의 옵셔널 바인딩과 불리언 조건문을 사용할 수 있습니다. 이 때는 쉼표를 이용하여 구분합니다. 만약 옵셔널 바인딩에 있는 값 중 하나라도 nil이거나 불리언 조건문이 false가 된다면 if 조건문은 거짓으로 판별됩니다.

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
  print("\(firstNumber) < \(secondNumber) < 100")
}
// "4 < 42 < 100"이 출력됨

위의 코드는 아래의 코드와 같습니다.

if let firstNumber = Int("4")
if let secondNumber = Int("42")
if firstNumber < secondNumber && secondNumber < 100 {
  print("\(firstNumber) < \(secondNumber) < 100")
}
// "4 < 42 < 100"이 출력됨

NOTE
if 조건문에서 옵셔널 바인딩으로 생성된 상수와 변수는 오직 if조건문 내에서만 사용할 수 있습니다. 이와 반대로, guard문으로 생성된 상수와 변수는 코드의 라인 내에서도 가능합니다. 자세한 사항은 Early Exit를 참고하세요.

암시적으로 언래핑된 옵셔널

위의 언급된 것과 같이, 옵셔널은 상수나 변수가 "값이 없다"는 것을 허용하기 위해 사용됩니다. 옵셔널은 if 조건문에서 실제로 옵셔널 내부에 값이 존재하는지 확인할 수 있으며 옵셔널 바인딩을 이용하여 값이 존재한다면 옵셔널의 값을 가져올 수 있습니다.

옵셔널이 항상 값을 가지도록 하는 프로그램 구조에 있어서는 옵셔널 값에 매번 접근할 때 값이 존재하는지 확인하고 언래핑을 하는 과정을 제거하는 것이 좋습니다. 왜냐하면 항상 값이 있다는 것을 전제로 하기 때문입니다.

이러한 종류의 옵셔널을 암시적으로 언래핑된 옵셔널이라고 합니다. 옵셔널으로 만들길 원하는 타입의 뒤에 느낌표를 이용하여 (String!) 암시적으로 옵셔널을 언래핑하여 사용할수 있습니다.

암시적으로 언래핑된 옵셔널은 옵셔널의 값이 존재한다는 것이 정의될 때 부터 사용될 때 까지 모든 곳에서 확신할 수 있을 때 사용합니다. 스위프트에서 최초로 암시적으로 언래핑된 옵셔널을 사용한 예는 클래스 초기화 구간입니다. 이 내용은 소유자 없는 참조와 암시적으로 언래피된 옵셔널 속성 (Unowned References and Implicitly Unwrapped Optional Properties)에서 설명합니다.

암시적으로 언래핑된 옵셔널은 일반적인 옵셔널입니다. 하지만 매번 옵셔널 값에 접근할 때 마다 언래핑하는 과정 없이 옵셔널이 아닌 값처럼 사용할 수 있습니다. 다음의 예제는 옵셔널 문자열과 암시적으로 언래핑된 옵셔널 문자열의 차이를 설명합니다.

let possibleString: String? = "옵셔널 문자열"
let forcedString: String = possibleString!
// 느낌표가 필요함
let assuemdString: String!= "암시적으로 언래핑된 옵셔널 문자열"
let implicitString: String = assumedString
// 느낌표가 필요 없음

암시적으로 언래핑된 옵셔널을 옵셔널로 하여금 자동적으로 언래핑하도록 권한을 주는 것과 같습니다. 옵셔널 값에 접근하기 위해 느낌표를 매번 사용하는 것보다 문자열을 선언할 때 옵셔널 타입 뒤에 느낌표를 한번만 사용하세요.

NOTE
만약 암시적으로 언래핑된 옵셔널이 nil이며 래핑된 값에 접근한다면 런타임 에러가 발생합니다. 이 결과는 값이 없는 일반적인 옵셔널뒤에 느낌표를 붙이는 것과 정확히 같습니다.

if assumedString != nil {
  print(assumedString!)
}
// "암시적으로 언래핑된 옵셔널 문자열"이 출력됨

옵셔널 바인딩을 이용하여 암시적으로 언래핑된 옵셔널을 사용할 수 있습니다.

if let definiteString = assumedString {
  print(definiteString)
}
// "암시적으로 언래핑된 옵셔널 문자열"이 출력됨

참고

변수가 이후에라도 nil이 될 수 있다면 암시적으로 언래핑된 옵셔널을 사용하지 마세요. 만약 변수의 생명주기 동안 nil값인지 확인하려면 항상 일반적인 옵셔널을 사용하세요.

에러 핸들링

여러분의 프로그램이 실행될 때 맞닥뜨릴 수 있는 에러에 대응하기 위해 에러 핸들링을 사용하세요. 값이 존재하거나 값이 존재하지 않는 것을 다루는 옵셔널과는 다르게, 에러 핸들링을 이용해 함수가 성공했는지 실패했는지에 대해 응답할 수 있으며 다른 프로그램의 부분에 에러를 전가할 수도 있습니다.

함수가 에러 조건을 만나면 error를 throw(던집니다). 그 함수를 호출한 대상은 에러를 catch (잡고) 적절하게 대처할 수 있습니다.

func canThrowAnError() throws {
  // 이 함수는 에러를 던질 수도 안 던질 수도 있음
}

함수가 throws 키워드를 포함하여 에러를 던질 수 있도록 합니다. 에러를 던질 수 있는 함수를 호출할 때 try키워드를 추가할 수 있습니다.

스위프트는 에러가 catch절에 의해 다루어질 때까지, 자동적으로 현재 범위의 바깥으로 에러를 전가할 수 있습니다.

do {
  try canThrowAnError() 
  // 에러가 던져지지 않을 때
} catch {
  // 에러가 던져졌을 떄
}

do문은 새로운 범위를 만듭니다. 이 범위는 하나 이상의 catch절에 에러를 전가할 수 있습니다. 다음의 예제는 에러 핸들링이 어떻게 다른 에러 조건에 대응할 수 있는지를 보여줍니다.

func makeASandwich() throws {
  //...
}

do {
  try makeASandwich()
  eatASandwich()
} catch SandwichError.outOfCleanDishes { // 깨끗한 접시가 없음
  washDishes()
} catch SandwichError.missingIngredients(let ingredients)  // 재료가 부족함 {
  buyGroceries(ingredients)
}

이 예제에서, makeASandwich()함수가 사용할 수 있는 깨끗한 접시가 없거나 재료가 부족하면 에러를 던질 것입니다. makeASandwich()는 에러를 던질 수 있으므로, try키워드의 표현식으로 makeASandwich()함수가 사용됩니다. do문 에서 함수 호출을 사용하여 던져질 수 있는 어떠한 에러든지 catch절로 전가됩니다.

만약 어떠한 에러도 던져지 않는다면, eatASandwich()함수가 호출됩니다. SandwichError.outOfCleanDishes의 경우와 일치하는 에러가 던져진다면 washDishes()에러가 호출됩니다. 또는, SandwichError.missingIngredients와 에러가 일치하면 catch패턴에서 처리된 ingredients라는 상수가 buyGroceries(_:)의 인자가 되어 호출됩니다.

에러를 던지고(Throwing), 잡고(catching), 전가(propagating) 하는 방법에 대한 자세한 설명은 Error Handling을 참고하세요.

단언과 선제조건

단언 (Assertions)선제조건 (preconditions)은 런타임 시점에 시도하는 확인 작업입니다. 코드가 모두 실행되기 전에 필수적인 조건이 충족되었는지 확실히 하고 싶을 때 사용할 수 있습니다. 단언과 선제조건의 불리언 조건이 참으로 판별이 되면코드는 평상시 처럼 실행됩니다. 하지만 그 조건이 거짓으로 판명되면 프로그램의 상태는 무효화되어 코드 실행이 종료됩니다. 코딩을 하는 동안 여러분이 기대하는 내용과 가정에 대해 표현하기 위해 단언과 선제조건을 사용할 수 있습니다. 단언과 선제조건은 마치 코드의 일부분 처럼 취급됩니다. 단언은 개발 중에 일어나는 실수와 부적절한 가정을 찾는 데 도움이 되며 선제조건은 개발 이후에 문제들을 찾아내는 데 도움이 됩니다.

런타임 시점에서 여러분이 기대하는 것을 검증하기 위해 단언과 선제조건은 코드 내에서 유용한 형태의 문서가 될 수도 있습니다. 위에서 다룬 에러 핸들링과는 다르게 단언과 선제조건은 예상한 에러로부터 복구하는데 사용되지 않습니다. 왜냐하면 실패한 단언과 선제조건은 프로그램의 상태가 무효함을 말하는 것이기 때문입니다. 따라서 실패한 단언에 대해 catch할 수 있는 방법은 없습니다.

하지만 유효한 데이터나 상태를 강제하도록 하는 것은 여러분의 앱이 예측가능한 범위에서 종료되도록 합니다. 무효한 상태가 발견되자마자 프로그램 실행을 멈추는 것은 피해를 제한할 수 있는데 도움이 됩니다.

단언과 선제조건의 차이는 검사가 이루어지는 시점입니다. 단언은 오직 디버그 빌드에서만 확인되지만 선제조건은 디버그와 제품 빌드 단계에서 검사됩니다. 제품 빌드 단계에서는 단언 안의 조건은 계산되지 않습니다. 개발 단계에서는 생산 단계에서의 성능에 영향을 주지않으면서 원하는 만큼의 단언을 사용해도 좋다는 의미입니다.

단언을 사용하는 디버깅

단언은 스위프트 스탠다드 라이브러리의 assert(::file:line:) 함수를 호출하여 사용합니다. 이 함수를 표현식에 전달합니다. 이 표현식은 참인지 것인지를 판별합니다.

예를 들어:

let age = -3
assert(age >=0, "사람의 나이는 0보다 작을 수 없습니다.")
// -3은 0보다 크거나 같지 않으므로 이 단언은 실패함

이 예제에서는 if age>=0에서 참임이 판별되면 코드 실행이 계속 됩니다. 즉 age가 음수가 되면 안됩니다. 만약 age가 음수라면 age >= 0이 거짓으로 판명이 나므로 단언은 실패하여 애플리케이션은 종료됩니다.

assert(age >= 0)

코드의 조건이 이미 검사되면 assertionFailure(_:file:line:) 함수를 사용하여 단언이 실패했는지 확인 할 수 있습니다.

if age > 10 {
  print("롤러코스터나 관람차를 탈 수 있어요.")
} else if age >= 0 {
  print("관람차를 탈 수 있어요")
} else {
  assertionFailure("사람의 나이는 0보다 작을 수 없어요.")
}

강제 선제조건

선제조건은 조건이 거짓일 가능성이 있는 경우에 사용하세요. 실행이 계속되기 위해 반드시 참이여야 합니다. 예를 들어, 선제조건은 서브스크립트가 범위를 벗어나는지 확인하기 위해 사용하거나 함수가 유효한 값으로 전달되었는지 확인하는데 사용합니다.

선제조건은 precondition(::file:line:)함수를 사용하여 작성합니다. 이 함수를 참 혹은 거짓인 표현식에 전달합니다. 또한, 조건이 거짓인 결과를 표시하기 위해 함수를 메시지에 전달합니다.

// 서브스크립트 구현
precondition(index > 0, "인덱스는 0보다 커야 합니다.")

참고

만약 언체크 모드(-Ounchecked)로 컴파일하면, 선제조건을 체크하지 않습니다. 컴파일러는 선제조건을 항상 참이여야 함을 가정합니다. 하지만 fatalError(:file:line:)함수는 최적화 설정에 상관 없이 항상 실행을 멈출 것입니다. 프로토타이핑과 초기 개발 시에 아직 구현이 완벽하지 않은 기능을 만들기 위해 fatalError(:file:line:) 함수를 사용할 수 있습니다. 왜냐하면 fatal errors는 단언과 선제조건와 다르게 최적화되지 않기 때문입니다.