아요 개발 일기

[Swift] The Basic (기초) 본문

Swift/The Swift Programming language

[Swift] The Basic (기초)

소진이 2022. 12. 30. 11:48

안녕하세요 :) 오늘은 Swift 기초에 대해서 알아보겠습니다!!

Swift의 특징부터 변수와 상수, 다양한 타입까지 함께 살펴볼까요??ㅎㅎ

이 글은 Swift 공식문서 The Swift 5.5 Language Guide - The Basic과 꼼꼼한 재은씨의 Swift : 문법편을 참고하여 작성하였습니다.

 


Swift는 iOS, macOS, watchOS, tvOS 앱을 개발하는 새로운 프로그래밍 언어입니다.

스위프트는 데이터 타입 추론 기능에 의해 컴파일러가 변수와 상수 타입을 결정하기때문에 동적 바인딩(Python, javascript)언어 인 것 같지만, 정적 바인딩(C, C++, object-C, Java)을 채용하고 있는 언어입니다.

 

만약, 정적 바인딩에대해 자세히 알고 싶으시면
정적 바인딩(Static binding)과 동적 바인딩(Dynamic binding) 을 참고하세요!

 

Q. 컴파일러는 정확히 하는 일이 뭐에요?

: 변수에 대입될 값을 검토하여 가장 적절한 타입을 추론하고 그에 맞는 메모리 공간을 확보한 다음, 값을 저장합니다. 


Swift 언어의 특징

 

Type

Swfit는 정수형 값은 Int, 부동 소수점(floating-point)값은 Double과 Float, 불린 값은(참 혹은 거짓)  Bool, 텍스트 데이터값은 String으로 C와 Objective-C의 모든 기본적인 데이터 타입의 자체 버전을 제공합니다. 

또한, Collection Type 으로는 Array, Set, Dictionary 3가지의 강력한 버전을 제공합니다.

 

Tuple

Swift는Tuple과 같은 Objective-C에는 없는 고급 타입을 도입했습니다. 튜플은 값을 그룹화해서 생성하고 전달하는 것이 가능한데,

때문에 함수로부터 합성된 단일 값으로 여러개의 값을 반환받기 위해 사용됩니다.

 

Optional

Swift는 값이 없는 것을 처리하는 옵셔널 타입을 도입했습니다.

옵셔널은 "그 곳에 값이 있고, x와 같다" 혹은 "그 곳에 값이 없다" 를 뜻합니다.

Objective-C에서의 nil 포인터보다 안전하고 쓰임새가 많으며, Swift의 가장 강력한 기능의 핵심입니다.

 

Type-safe

Swift는 타입에 안전한(type-safe)언어이며, 값의 타입을 명확히하는데 도움을 주는 언어입니다.

코드에서 String이 필요한 경우, type-safety는 실수로 Int를 전달하는 것을 막아줍니다.

마찬가지로, type-safety는 옵셔널 String이 옵셔널이 아닌 String이 필요한 곳에 전달되는 것을 막아줍니다.

Type safety는 개발 단계에서 가능한 빨리 오류를 잡고 수정하는데 도움을 줍니다.

 

등등 Swfit언어에는 많은 특징이 있습니다!

이렇게 축약된걸 읽으니까 잘 모르겠죠?! 아래에서 더 자세히 알아볼까요?


Constants and Variavles (상수와 변수)

상수와 변수는 특정 타입(10 숫자 또는 "Hello" 문자열)을 값과 연결(저장)합니다.

 

상수 (Constant)

- 할당된 값 변경 불가능

- 코드 내에서 반복적으로 사용되는 값이 있을 경우에 유용

 

변수 (Variavles)

- 할당된 값 변경 가능

 

 

Declaring Constants and Variavles (상수와 변수 선언하기)

let maximumNumberOfLoginAttempts = 10 // 상수 선언
var currentLoginAttempt = 0 // 변수 선언

var x = 0.0, y = 0.0, z = 0.0 // 변수 여러개 선언

 

NOTE
코드에서 저장된 값을 변경하지 않는 경우, 항상 let 키워드 상수를 선언해야합니다.
변수는 값을 변경할 필요가 있을때만 사용합니다.

 


Q. var(변수)로 다 선언하면 더 편할 것 같은데, 왜 굳이 let(상수)가 있어야하나요?

A. 프로그래밍 소스 관리 목정상 변하지 않는 값은 상수에 저장하는 것이 훨씬 효율적입니다.

실수로 값이 변경되는 일을 막을 수 있고, 값의 성격이 명확히 분류되므로 관리하기도 용이하기때문입니다.

혹시 상수와 변수 중 어떤 것을 사용해야할지 기준이 모호하다면 일단 변수로 작성하면됩니다!

(만약, 변경되지 않으면 상수로 바꾸도록 컴파일러가 조언합니다.)

 

Q. 위의 예제를보면 따로 Type을 명시하지 않았는데, 이렇게 사용하는게 가능한가요?

A. 네 가능합니다. Swift에는 '타입 추론기'라능 기는 모듈을 컴파일러에 별로도 내장하여, 항상 최적의 타입을 추론해주는 기능이 있습니다. 따로 선언하지 않아도 스위프트에서 타입을 정해주는 것이죠!! 참 편리하죠? 자세한 이야기는 타입 추론(Type inference)과 타입 어노테이션(Type annotation)을 참고하세요!


 

Type Annotations (타입 명시하기)

 

var welcomeMessage: String // 타입 명시
welcomeMessage = "Hello" // String 값 저장
var red, green, blue: Double // 여러개 변수 정의

 

 

 

Naming Constants and Variables (상수와 변수 이름짓기)

 

1. 알파벳과 한글 자음 및 모음, 아라비아 숫자를 사용할 수 있으며, 특수 기호나 한자, 이미지용 바이너리 코드까지 사용할 수 있습니다.

// 영어 및 숫자, 언더바로 정의
var str = "문자열"
var initInt34 = 37
var init_Int = 100


// 한글 및 한글 초성, 중성으로 정의
var 김소진 = "저자"
var ㄱㅊㅇㅇ = "괜찮아요"
var ㅏㅑㅓㅕㅗ = "가갸거겨고"


// 한글 초성과 알파벳을 혼용하여 정의
var aㅁㄴs = 30.142
var 마3t = 12345


// 특수 기호를 사용하여 정의
var π = 3.14
var ⓖ = false


// 이미지 바이너리를 사용하여 정의
var 😁 = "C"
var 😍 = "l"


// 한자를 사용하여 정의
var 昭陳 = "소진"
var 韩国 = "한국"


// 위 예를 모두 함께 섞어서 정의
var aㅁB수23Ω😘昭 = "이것저것"

 

2. 연산자와 혼동할 수 있는 [+,-,*./]및 공백은 변수, 상수명에 사용할 수 없습니다. 단, _(언더바)는 사용할 수 있습니다

// 연산자와 공백은 변수나 상수명에 들어가면 안 됨

var abc+t = "abc plus t" // 값을 더하는 연산자라서 사용 불가
let abc-t = "abc minnus t" // 값을 빼는 연산자라서 사용 불가
let abc t = "abc space t" // 변수명이 어디까지 인지 구분할 수 없어서 사용 불가


//언더바는 예외적으로 사용 가능
var so_jin = "so underbar jin"

 

3. 스위프트에서 예약어나 키워드로 등록되어 있는 변수나 상수명에 사용할 수 없습니다. 단, 대소문자를 바꾸어 사용하는 것은 가능합니다. (`로 묶으면 예약어나 키워드도 사용가능하지만 추천하지 않음.)

// 불가능한 경우

var class = 1 // 클래스 정의 키워드
var enum = 2 // 열거형 정의 키워드
var struct = 3 //구조체 정의를 위한 키워드
var extension = 4 //확장을 위한 키워드
var protocol = 5 //프로토콜 정의를 위한 키워드
var as = 6 //타입 캐스팅을 위한 키워드


// 허용되는 경우
var Class = 1
var Enum = 2
var Struct = 3
var Extension = 4
var Protocol = 5
var As = 6

 

4. 변수, 상수명의 첫 번째 자리에 숫자가 올 수 없습니다.

// 첫 번째 자리에는 숫자를 사용할 수 없음
var 1abc = 123
var 2bcd = 345


// 두 번째 자리부터는 숫자 사용 가능
var a123bc = 123
var b2cd = 345

 

 

Printing Constants and Variables (상수와 변수 출력하기)

상수나 변수의 현재 값을 print(_:separator:terminator:) 함수로 출력할 수 있습니다.

 

print(friendlyWelcome) // Prints "Bonjour!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"

 


Comments (주석)

--- 한 줄 주석 ---
// This is a comment.

--- 여러줄 주석 ---
/* This is also a comment
but is written over multiple lines. */

/* This is the start of the first multiline comment.
 /* This is the second, nested multiline comment. */
This is the end of the first multiline comment. */

코드를 한꺼번에 주석처리하고 싶을때는 해당 코드를 드래그 한 후, control + / 누르면 됩니다.

 


Semicolons(세미콜론)

Swift는 원한다면 세미콜론(;)을 사용할 수 있지만, 꼭 작성되도록 요구하진 않습니다.

하지만, 여러 문장을 구분하길 원할때에는 세미콜론이 필요합니다.

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

 


Integers (정수)

정수에는 아래와 같이 두가지 종류가 있으며,  8, 16, 32, 64 비트 형식의 정수를 제공합니다.

부호가 있는 정수(양수+, 0, 음수-) - Int (8비트 - Int8타입)

부호가 없는 정수(양수, 0) - UInt (32비트 - UInt32타입)

 

Integer Bounds (정수 범위)

min과 max 프로퍼티로 각 정수 타입의 최소값과 최대값을 사용할 수 있습니다.

let minValue = UInt8.min  // minValue is equal to 0, and is of type UInt8
let maxValue = UInt8.max  // maxValue is equal to 255, and is of type UInt8

 

Int

거의 코드에서 정수의 크기(비트)를 지정할 필요는 없습니다. 

Swift는 현재 플랫폼의 기본 워드(word)크기와 동일한 크기를 가진 정수 타입 Int를 제공합니다.

  • 32비트 플랫폼에서, Int는 Int32와 동일한 크기
  • 64비트 플랫폼에서, Int는 Int64와 동일한 크기

특정 크기의 정수로 작업해야하는 경우가 아니라면, 코드에서 정수 값은 항상 Int를 사용합니다.

이는 코드 일관성과 운영성에 도움이 됩니다. 32비트 플랫폼에서, Int는 -2,147,483,648과 2,147,483,647 사이의 모든 값을 저장할 수 있고 많은 정수를 저장하기에 충분히 큽니다.

 

UInt

또한, Swift는 현재 플랫폼의 기본 워드(word)와 동일한 크기의 부호없는 정수 타입 UInt를 제공합니다.

  • 32비트 플랫폼에서, UInt는 UInt32와 같은 크기입니다.
  • 64비트 플랫폼에서, UInt는 UInt64와 같은 크기입니다.

 

NOTE
플랫폼의 기본 워드 크기와 같은 크기의 부호없는 정수 타입이 필요할때만 UInt를 사용합니다.
하지만 정수값이 굳이 음수가 아니어도 거의 Int 타입을 사용하는게 좋습니다.
정수 값에 대해 Int를 일관성 있게 사용하면 Type Safety and Type Inference에서 설명된것처럼
코드의 운용성을 도와주며, 다른 숫자 타입으로 변환할 필요가 없고, 정수 타입 추론을 일치시킵니다.

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

부동소수점 숫자는 3.14159, 0.1, -273.15와 같은 분수로 된 숫자입니다.

부동소수점 타입은 정수 타입보다 훨씬 더 넓은 범위의 값을 표현할 수 있고 저장할 수 있습니다.

Swift는 두개의 부호있는 부동소수점 숫자 타입을 제공합니다.

  • Double은 64비트 부동소수점 숫자를 표현
  • Float은 32비트 부동소수점 숫자를 표현

 

주의
Double은 최소 15자리의 정확도를 가지는 반면에, Float은 6자리의 정확도를 가질수 있습니다. 사용하는 적절한 부동소수점 타입은 코드에서 작업하는데 필요한 값의 특성과 범위에 따라 달라집니다. 두 타입 모두 사용가능한 경우에는 Double이 사용됩니다.

 


Type Safety and Type Inference (타입 안전성과 타입 추론)

Swift는 타입에 안전한(type-safe)언어이기때문에 코드에서 사용할 수 있는 값의 타입을 명확히 하는 것을 권장합니다.

코드를 컴파일할때 타입 검사(type check)를 수행하고 일치하지 않는 타입을 오류 표시합니다. (개발 단계에서 가능한 빨리 오류를 잡고 고칠수 있음)

 

하지만, 그렇다고 모든 타입을 지정해줄 필요는 없습니다.

Swift에는 아까 위에서 말한 것처럼 적절한 타입을 정해주는 타입 추론(type inference)을 사용합니다.

이 타입 추론때문에, Swift는 C나 Objective-C와 같은 다른 언어보다 타입 선언이 훨씬 더 적게 필요합니다.

 

타입 추론은 초기 값으로 상수나 변수를 선언할때 특히 유용합니다.

이는 종종 상수나 변수를 선언하는 시점에 리터럴 값을 할당해서 처리합니다.(리터럴 값은 아래 예제의 42와 3.14159와 같이 코드에서 직접 표현하는 값입니다.)

 

예를들어, 42의 리터럴 값을 타입이 무엇인지 알리지 않고 새로운 상수로 할당하는 경우, Swift는 정수로 보이는 숫자로 초기화되었기 때문에, 상수가 원하는 것이 Int가 되도록 추론합니다.

 

또한, Swift는 대부분의 부동 소수점 숫자를 추론할때 Float 대신에 Double을 선택합니다.

let meaningOfLife = 42
// meaningOfLife is inferred to be of type Int

let pi = 3.14159
// pi is inferred to be of type Double

let anotherPi = 3 + 0.14159
// anotherPi is also inferred to be of type Double

Numeric Literals (숫자 리터럴)

 

정수 리터럴

  • 10진수(decimal) 숫자는 접두사(prefix)없음
  • 2진수(binary) 숫자는 0b 접두사 사용
  • 8진수(octal) 숫자는 0o 접두사 사용
  • 16진수(hexadecimal) 숫자는 0x 접두사 사용

let decimalInteger = 17
let binaryInteger = 0b10001       // 17 in binary notation
let octalInteger = 0o21           // 17 in octal notation
let hexadecimalInteger = 0x11     // 17 in hexadecimal notation

 

 

부동 소수점 리터럴 (floating-literals) 

  • 10진수(접두사 없음)
  • 16진수( 접두사 0x)

 

지수 표현

부동 소수점 리터럴은 대문자 또는 소문자 e를 사용하여 지수를 표현할 수 있습니다.

16진수 부동 소수점은 대문자나 소문자 p로 지수를 표시합니다.

 

10진수

  • 1.25e2 means 1.25 x 102, or 125.0.
  • 1.25e-2 means 1.25 x 10-2, or 0.0125.

16진수

  • 0xFp2 means 15 x 22, or 60.0.
  • 0xFp-2 means 15 x 2-2, or 3.75.

 

Q. 그럼 음수의 지수는 어떻게 표현하나요?

A. 음수의 제곱승은 나누기로 표현됩니다. 예를 들면 27 / (3 * 2) = 4.5 와 같습니다

 

아래 코드에 있는 값들은 모두 10진수 12.1875 값을 표현한 것입니다.

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

 

추가 서식(extra formatting)

숫자 리터럴은 읽기 쉽게하기 위해 추가 서식을 포함할 수 있습니다.

정수와 부동소수점 모두 읽기 쉽게 추가적으로 0을 채워넣을수 있고 밑줄(_)을 사용할 수 있습니다.

두 포멧 타입 모두 리터럴 기반의 값에서는 아무런 영향을 미치지 않습니다.

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

 


 

Numeric Type Conversion (숫자 타입 형변환)

정수형 상수와 변수는 일반적인 경우에는 Int형을 사용합니다.

이렇게 명시적인 크기 타입을 사용하면, 값 오버플로우가 발생하는 것을 잡고 사용되는 데이터의 특성을 암시적으로 문서화하는데 도움이 됩니다.

 

Integer Conversion (정수형 형변환)

정수형 상수나 변수에서 저장할 수 있는 범위는 각 타입에따라 다릅니다.

Int8은 -128~127 사이의 숫자들을 저장할 수 있는 반면에, UInt8은 0~255 사이의 숫자들을 저장할 수 있습니다.

이렇게 크기가 지정된 정수 타입의 상수와 변수가 맞지 않은 숫자를 사용하는 경우에는 컴파일 오류가 발생합니다.

let cannotBeNegative: UInt8 = -1
// UInt8 can't store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 can't store a number larger than its maximum value,
// and so this will also report an error

값의 범위가 다 다르기때문에, 상황에 따라 형변환할 숫자 타입을 선택해야합니다.

이러한 방법은 숨겨진 형변환 오류를 막아주고 코드에서 타입 형변환 의도를 명시적으로 만드는데 도움이됩니다.

 

아래는 다른 타입의 숫자끼리 더하기위해서, 형변환을 해주는 코드입니다.

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

SomeType(ofInitialValue)는 Swift의 타입 초기화를 호출하고 초기 값을 전달하는 기본적인 방법입니다.

내부적으로, UInt16은 UInt8값을 받은 초기화(initializer)를 가지고, 이 초기화는 기존 UInt8로부터 새로운 UInt16을 만드는데 사용됩니다. 

새로운 타입(자신만의 타입 정의를 포함해서)을 허용하는 초기화를 제공하기 위해서 기존 타입을 확장하는 것은 Extensions에서 다룹니다.

 

Integer and Floating-Point Conversion (정수와 부동소수점 형변환)

위의 정수형 형변환처럼 정수와 부동 소수점 형변환도 SomeType(ofInitialValue) 방법을 사용합니다.

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double

 

부동 소수점 값이 Int 값으로 변환될때는 소숫점 뒷자리는 짤리게됩니다.

let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int

 

NOTE
숫자 상수와 변수를 결합하는 규칙은 숫자 리터럴에 대한 규칙과는 다릅니다.
숫자 리터럴이 자체적으로 명시적인 타입을 가지고 있지 않기 때문에, 리터럴 값 3은 리터럴 값 0.14159에 직접 추가될수 있습니다.
이러한 타입은 컴파일러에 의해 처리되는 시점에 추론됩니다.

 


Type Aliases (타입 별명)

타입 별명은  typealias 키워드를 사용하며, 기존 타입에대한 또 다른 이름을 정의합니다.

외부 소스에서 특정 크기의 데이터를 작업할때, 기존 타입을 문맥상 더 적절한 이름으로 참조하고자 할때 유용하게 사용됨니다.

typealias 키워드로 별명을 정의하고나면 원래 이름을 사용할 수 있는 곳 어디든 사용할 수 있습니다.

typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound is now 0

 


 

Booleans

Boolean(bool) 값은 true 또는 false만될 수 있으며, 논리 값이라고 합니다.

let orangesAreOrange = true
let turnipsAreDelicious = false

 

Booleans 값은 if문봐 같은 조건문으로 작업할때 특히 유용합니다. 

if turnipsAreDelicious {
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}
// Prints "Eww, turnips are horrible."

 

i == 1 비교 결과는 Bool 타입이기때문에 두번째 예제에서 타입 검사를 통과합니다.

i == 1같은 비교문은 Basic Operators에서 다루게됩니다.

 

// 오류
let i = 1
if i {
    // this example will not compile, and will report an error
}

// 가능
let i = 1
if i == 1 {
    // this example will compile successfully
}

Swift에서의 타입 안정성의 다른 예제처럼, 이러한 접근법은 실수로 발생하는 오류를 피하게해주고 코그의 특정 섹션의 의도를 항상 명확하게 보장해줍니다.


Tuple (튜플)

튜플은 여러개의 값을 단일 합성 값으로 그룹화 합니다. 튜플에 있는 값들은 어떤 타입이든 가능하고, 각각 같은 타입이 아니어도 됩니다.

 

예제를 한번 살펴 볼까요?

http404Error 상수에 (404 - 정수, "Not Found" - String) 서로 다른 데이터 타입 값들이 들어가있습니다.

바로 이 http404Error를 "(Int, String)타입의 튜플(Tuple)" 이라고 설명할 수 있습니다.

let http404Error = (404, "Not Found")
// http404Error is of type (Int, String), and equals (404, "Not Found")

 

튜플은 여러가지 타입의 순서로 생성할 수 있고, 많은 타입도 포함할 수 있습니다.

아래 예시처럼 튜플의 내용을 별도의 상수나 변수로 분해하여 사용할 수도 있습니다.

let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// Prints "The status code is 404"
print("The status message is \(statusMessage)")
// Prints "The status message is Not Found"

 

튜플 값의 일부만 필요한 경우, 튜플을 분해할때 밑줄(_)로 튜플의 일부를 무시합니다.

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// Prints "The status code is 404"

 

또는 0으로 시작하는 인덱스 번호를 사용하여 튜플의 개별 요소의 값을 사용합니다.

print("The status code is \(http404Error.0)")
// Prints "The status code is 404"
print("The status message is \(http404Error.1)")
// Prints "The status message is Not Found"

 

튜플을 정의할때 튜플에 있는 개별 요소들의 이름도 지정해줄 수 있습니다.

let http200Status = (statusCode: 200, description: "OK")

 

튜플에 있는 요소들이 이름이 있는 경우, 이러한 요소들의 값을 사용하기 위해 요소 이름을 사용할 수 있습니다.

print("The status code is \(http200Status.statusCode)")
// Prints "The status code is 200"
print("The status message is \(http200Status.description)")
// Prints "The status message is OK"

 

튜플은 함수의 반환 값으로 특히 유용합니다.

함수는 웹페이지 검색의 성공 또는 실패를 설명하기 위해 (Int, String) 튜플 타입을 반환합니다.

반환되는 튜플은 두개의 각기 다름 값으로 되어있으며, 각각 다른 타입이며, 이 함수는 단일 타입의 단일 값을 반환하는 것보다 더 유용한 정보를 제공합니다. 더 자세한 정보는  Functions with Multiple Return Values 를 보세요.

 

NOTE
튜플은 관련된 값들을 임시로 그룹화하는데 유용합니다.
복잡한 데이터 구조를 만들기에는 적합하지 않는데, 데이터를 임시로 사용하는 것이 아니라 지속적으로 사용하는 경우에는
튜플보다는 클래스나 구조체로 모델링합니다. 자세한 정보는 Structures and Classes를 참고하세요.

 

튜플에 대해 더 자세히 알아보고 싶다면 Collection Type 에 있는 Tuple 부분을 참고해주세요!


Optional (옵셔널)

옵셔널은 값이 없을수도 있는 상황에서 사용합니다.

옵셔널은 값이 있고 그 값을 사용하기위해 옵셔널은 언래핑(unwrap)할 수 있거나, 값이 전혀 없음 둘 중 하나입니다.

 

아래 코드는 옵셔널 값이 없을때 처리하는 방법에 대한 예제입니다.

아래 예제는 String 을 Int로 형변환하는 초기화를 하고있지만, 언제나 String 값을 Int로 변경할 수 있는 것은 아닙니다.

예를 들어 문자열 "123"은 정수 값 123으로 형변환이 가능하지만, 문자열 " hello, world"는 분명한 숫자값이 없습니다. 

 

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"

 

이처럼 초기화를 실패할 수도 있기때문에, Int보다는 optional Int를 반환합니다.

optional Int는 Int?로 표현되며 물음표(?)는 옵셔널을 포함하는 것을 가리킵니다.

즉, Int 값을 포함하거나 아무 값도 가지지 않는 것을 의미합니다. 

 

nil

값이 없는 상태를 만들기위해 특별한 값 nil을 할당해서 옵셔널 변수를 설정합니다.

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value
NOTE
옵셔널이 아닌 상수와 변수에 nil을 사용할 수 없습니다.
코드에서 상수나 변수가 특정 조건에서 값이 없이 작업해야하는 경우에, 항상 적절한 타입의 옵셔널 값으로 선언합니다.

 

기본 값을 제공하지 않고 옵셔널 변수를 선언하는 경우, 그 변수는 nil로 설정됩니다.

var surveyAnswer: String?
// surveyAnswer is automatically set to nil
NOTE
Swift의 nil은 Objective-C에서의 nil과 같지 않습니다.
Objective-C에서의 nil은 존재하지 않는 객체에 대한 포인터입니다.
Swift에서 nil은 포인터가 아니라 특정 타입의 값이 없는 것을 의미합니다.
객체 타입뿐만아니라, 어떤 타입의 옵셔널도 nil로 설정할 수 있습니다. 

 

If Statements and Forced Unwrapping (if문과 강제 언래핑)

옵셔널이 nil인지 비교하여 옵셔널 값을 포함하고 있는지 확인하기 위해 if문을 사용할 수 있습니다.

 

옵셔널이 값을 가지고 있는 경우, nil과 "같지 않다"로 간주합니다.

if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// Prints "convertedNumber contains some integer value."

 

옵셔널에 값이 있다고 확신하는 경우, 옵셔널 이름 끔에 느낌표(!)를 추가해서 값을 사용할 수 있습니다.

느낌표(!)는 "옵셔널이 확실히 값을 가지고 있는 것을 알고 있으며, 이를 사용합니다." 라는 것을 의미합니다.

이는 옵셔널 값의 강제 언래핑이라고 합니다.

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
NOTE
옵셔널 값이 존재하지 않을때 !를 사용하려고 하면 런타임 오류가 발생합니다. 
값을 강제 언래핑하기 위해 !를 사용하기 전, 옵셔널에 nil이 아닌 값을 포함하고 있는지 확인해야합니다.

 

Optional Binding (옵셔널 바인딩)

옵셔널에 값을 가지고 있는지 알기위해 옵셔널 바인딩을 사용하고, 만약 값이 존재한다면 그 값을 임시 상수나 변수로 사용할 수 있습니다.

옵셔널 바인딩은 옵셔널 안에 값을 확인하기위해, if와 while문에서 사용될 수 있고, 단일 동작으로 그 값을 상수나 변수로 추출합니다.

 

if문에 대한 옵셔널 바인딩을 다음과 같이 작성합니다.

if let constantName = someOptional {
    statements
}

 

아래 코드는 다음과 같이 읽을 수 있습니다.

"Int(possibleNumber)으로 반환된 옵셔널Int(Int?)가 값을 가지고 있는 경우, 옵셔널에 포함된 값을 새로운 상수 actualNumber에 설정합니다."

if let actualNumber = Int(possibleNumber) {
    print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
    print("The string \"\(possibleNumber)\" couldn't be converted to an integer")
}
// Prints "The string "123" has an integer value of 123"

반환이 성공한 경우, actualNumber 상수는 if문의 첫번때 분기안에서만 사용할 수 있습니다.

옵셔널 바인딩은 상수와 변수 모두 사용할 수 있습니다.

if문의 첫번째 분기 안에 actualNumber의 값을 처리하기 원하는 경우, if var actualNumber을 작성할 수 있고, 그 옵셔널의 포함된 값은 상수가아닌 변수로 사용됩니다.

 

하나의 if문 안에서 여러개의 옵셔널 바인딩과 Bollean 조건문을 콤마(,)로 구분해서 포함할 수도 있습니다.

옵셔널 바인딩에는 모든 값이 nil이거나 모든 Boolean 조건이 false가 되는 경우, if문의 조건은 false로 처리됩니다.

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

if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// Prints "4 < 42 < 100"
NOTE
if문 안에서 옵셔널 바인딩으로 생성된 상수와 변수는 if문 본문에서만 사용가능합니다.
하지만, guard문으로 생성된 상수와 변수는 guard문 아래의 코드에서만 사용 가능 하며, Early Exit에서 설명되어있습니다.

 

Implicitly Unwrapped Optionals (묵시적으로 언래핑된 옵셔널)

 

프로그램 구조상으로 옵셔널이 처음 설정된 이후 명확하게 항상 값을 가지고 있어야할때가 있습니다.

이 경우에는 항상 가장 안전한 값을 가지고 있다고 가정하기 때문에, 옵셔널 값을 검사하고 언래핑하는 것이 유용합니다.

 

위와 같은 경우에 옵셔널이 묵시적으로 해제되는 옵셔널(Implicitly Unwrapped Oprtional)을 사용합니다.

해제 구문은 간단합니다. 옵셔널로 만들고자하는 타입의 뒤에 물음표(String?) 대신에 느낌표(String!)를 붙여서 묵시적으로 해제된 옵셔널을 작성합니다.

 

묵시적 해제 옵셔널은 옵셔널 값이 처음 정의된 직후에 존재하는 것이 확인되고, 그 이후에 모든 시점에 반드시 값이 존재할 때 유용하게 사용됩니다. 

Swift에서 묵시적 해제 옵셔널의 주된 용도는 클래스를 초기화할때이며, Unowned References and Implicitly Unwrapped Optional Properties 에서 설명되어 있습니다.

 

묵시적 해제 옵셔널은 일반 옵셔널 바인딩이지만, 사용할때마다 옵셔널을 해제할 필요없이 옵셔널이 아닌 값처럼 사용될 수 있습니다.

아래 예제는 옵셔널 문자열과 묵시적 해제 옵셔널 문자열간의 차이점을 보여줍니다.

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation point

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation point

묵시적 해제 옵셔널이 사용될때마다 자동으로 해제되도록 하기 위해 옵셔널에 권한을 주는 것처럼 생각할 수 있습니다.

매번 옵셔널의 이름 뒤에 느낌표(!)를 사용하는 대신에, 선언할 때 옵셔널 타입 뒤에 느낌표(!)를 붙입니다.

 

묵시적 해제된 옵셔널이 nil이고 감싸져있는 값을 사용하려는 경우 오류가 발생합니다.

let optionalString = assumedString
// The type of optionalString is "String?" and assumedString isn't force-unwrapped.

 

묵시적으로 해제된 옵셔널이 nil 값인지 확인하기위해, 일반 옵셔널처럼 처리할 수 있습니다.

if assumedString != nil {
    print(assumedString!)
}
// Prints "An implicitly unwrapped optional string."

 

또한 옵셔널 바인딩과 묵시적 해제 옵셔널을 사용하여 단일 구문을 검사하고 언래핑 할 수 있습니다.

if let definiteString = assumedString {
    print(definiteString)
}
// Prints "An implicitly unwrapped optional string."

 

NOTE
나중에라도 nil이 될 수 있는 가능성이 있을때는 묵시적 해제된 옵셔널을 사용하지 마세요.
변수를 사용하는 동안 nil 값을 검사할 필요가 있는 경우에는 항상 일반 옵셔널 타입을 사용해야합니다.

Error Handling (오류 처리)

 

프로그램이 실행되는 동안 발생할 수 있는 오류 조건을 처리하기 위해 오류처리를 사용합니다.

 

옵셔널과는 다르게, 함수의 성공이나 실패를 전달하기 위해 값의 유무를 확인할 수 있으며, 오류 처리를 통해 오류의 근본적인 원인을 판별하고, 필요한 경우 프로그램의 다른 부분으로 오류를 전달합니다.

 

함수가 오류 상태가 될 때, 오류를 던집니다.

함수의 호출자는 오류를 잡을 수(catch)있고 적절히 처리 할 수 있습니다.  

func canThrowAnError() throws {
    // this function may or may not throw an error
}

함수는 선언에서 throw 키워드를 포함함으로써 오류를 던질 수 있습니다.

오류를 던질수 있는 함수를 호출할때, try 키워드를 표현식의 앞에 붙입니다.

 

Swift는 자동으로 catch절에 의해 처리될 때까지 현재 범위의 바깥으로 오류를 전달합니다.

do {
    try canThrowAnError()
    // no error was thrown
} catch {
    // an error was thrown
}

 

do 구문은 하나 이상의 catch절에 오류를 전달할 수 있는 새로운 포함 범위를 만듭니다.

 

다음은 다른 오류 조건을 처리하기위해 사용될 수 있는 오류 처리방법 예제입니다.

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

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

이 예제에서  makeASandwich() 함수는 접시가 비었거나 재료가 빠진 경우 오류를 던질 것입니다.

makeASandwich()는 오류를 던질 수 있기때문에, 함수 호출은 try 표현식으로 감싸져야 합니다.

do 구문에서 함수를 래핑함으로써 모든 오류는 catch절에 전달 될 것입니다.

 

오류가 없는 경우, eatASandwich() 함수가 호출됩니다.

SendwichError.outOfCleanDishes와 일치하는 오류가 발생하는 경우에 washDishes()함수가 호출될 것입니다.

SandwichError.missingIngredients와 일치하는 오류가 발생하는 경우에는 catch패턴에 의해 연관된 [String] 값으로 buyGroceries(_:)함수가 호출됩니다.

 

오류 던지기(Throwing), 잡기(catching), 전달하기(propagating)은 Error Handling 에서 자세히 다루게됩니다.


Assertions and Preconditions (주장 및 전제 조건)

Assertions과 Preconditions은 런타임으로 발생하는 것을 검사합니다.

추가 코드를 실행하기 전에 필요한 조건을 만족하는지 확인하기 위해 사용합니다.

Assertion과 Precondition의 Boolean 조건이 true가 되는 경우, 평사시처럼 코드는 계속 실행됩니다.

조건이 false가 되는 경우, 프로그램의 현재 상태는 유효하지 않게 됩니다. 코드 실행은 끝나고 앱은 종료됩니다.

 

코딩하는동안 가정한 것과 기대한 것을 표현하기 위해 assertion과 preconditions을 사용하며, 코드의 일부처럼 포함할 수 있습니다.

Assertions은 개발하는 도중에 실수한 것과 잘못 가정한 것을 찾는데 도움이 되고,

Preconditions은 production에서 문제를 발견하는데 도움이됩니다.

 

런타임에 기대한 것을 확이하는 것 외에도, assertions와 preconditions는 코드내에서 유용한 문서 형식이 됩니다.

위의 Error Handling에서 논의했던 오류 조건과는 다르게 assertions와 preconditions는 복구하거나 예상된 오류에 대해 사용되지 않습니다. 실패한 assertions와 preconditions는 잘못된 프로그램 상태를 가리키기 때문에, 실패한 assertion은 잡을 수 없습니다.

 

Assertions와 Preconditions는 유효하지 않는 조건이 발생하지 않도록 코드를 설계하는데에 사용할 수 없습니다.

하지만, 유효하지 않은 상태가 발생한 경우에는 유효한 데이터가 되도록하고,앱이 예측 가능하게 종료할때 사용하여 문제에대해 디버깅을 쉽도록 도움을 줍니다.

유효하지 않은 상태가 감지되는 즉시 실행을 멈추는 것은 유효하지 않은 상태로 인해 발생되는 손상을 최소화하는데 도움이됩니다.

 

Assertions와 Preconditions은 검사할때 차이점이있습니다.

Assertions은 디버깅 빌드에서만 검사되지만, Preconditions은 디버깅과 production  빌드 모두에서 검사됩니다.

production 빌드에서, assertions 안쪽의 조건은 처리되지 않습니다.

이는 개발 단계에서 원하는 만큼의 많은 assertions를 사용할 수 있는 것을 의미하고, 제품의 성능에 영향을 미치지 않습니다.

 

 

Debugging with Assertions(주장 디버깅하기)

Swift 표준 라이브러리로부터 assert(:file:line) 함수를 호출해서 assertion을 작성합니다.

이 함수는 true 또는 false를 처리하는 표현식을 전달하고 그 조건식의 결과가 false인 경우에 보여주기 위한 메시지를 전달합니다.

예를들어,

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 isn't >= 0.

if age >= 0 이 true가 되면 코드 실행은 계속되고 age의 값이 음수가 아닌 경우 입니다.

age의 값이 음수인 경우, age >= 0은 false로 처리되고, assertion은 실패하며 앱은 종료됩니다.

 

아래와 같이 조건이 평범하게 반복될때 assertions은 메세지를 생략할 수 있습니다.

assert(age >= 0)

 

조건을 이미 확인한 코드의 경우일때는 assertions가 실패되는 것을 나타내는 assertionFailure(_:file:line:)함수를 사용합니다.

if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age >= 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

 

 

Enforcing Proconditions(필수조건 적용하기)

조건이 false될 가능성이 있지만, 코드를 계속 실행하기 위해서는 확실히 true가 되어야하는경우에 preconditions을 사용합니다.

예를들어, 서브 스크립트가 범위를 벗어나지 않았는지 검사하기 위해 또는 함수에 유효한 값이 전달되었는지 확인하기 위해 필수조건을 사용합니다.

 

필수조건을 precondition(_:_:file:line:) 함수를 호출해서 작성합니다.

함수에 true 또는 false로 처리하는 표현식을 전달하고 조건의 결과가 false인 경우에 보여주기 위한 메시지를 전달합니다.

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

또한 실패가 발생했음을 가리키기 위해 preconditionFailure(_:file:line:)함수를 호출할수 있습니다 - 예를들어, switch문의 default case가 있지만, 모든 유효한 입력 데이터를 switch의 다른 case들 중 하나로 처리해야 합니다.

NOTE
검사하지 않는 모드(-Ounchecked)에서 컴파일하는 경우에 preconditions은 검사되지 않습니다. 컴파일러는 preconditions이 항상 true로 가정하고, 코드에 따라 적절하게 최적화합니다.
하지만, fatalError(_:file:line) 함수는 최적화 설정과는 무관하게, 항상 실행을 멈춥니다.

프로토타입과 아직 구현되지 않은 기능을 개발하기 위해서 fatalError(_:line:) 함수를 사용할 수 있으며, 구현할 부분에 fatalError("Unimplemented")을 작성합니다.
치명적인 오류는 최적화되지 않기 때문에, assertions나 preconditions과는 다르며,
구현된 부분을 만나면 항상 실행이 중단됩니다.