본문 바로가기
프로그래밍/Swift

[Swift] Basic 문법

by 별준 2021. 12. 12.

References

Contents

  • Swift 기본 문법

와이프는 디자인 쪽 일을 하는데, 얼마전에 와이프와 앱을 함께 만들어보기로 했습니다. 둘 다 iOS 앱 쪽은 처음이니 서로 공부하며 준비를 하기로 해서 틈틈히 swift 언어에 대해서 공부하려고 합니다. 일을 하면서 공부하기 때문에 너무 깊게 살펴보지 않고 스윽 훑어보는 식으로 공식 사이트에서 제공되는 문서를 보며 공부하고 정리해 볼 예정입니다.

 


Swift ?

Swift는 iOS, macOS, watchOS, tvOS app 개발을 위한 애플의 프로그래밍 언어입니다. Swift의 많은 부분은 C와 Objective-C와 닮았습니다. 정수를 위한 Int, 부동소수점 값을 위한 Double과 Float, Bool, String을 포함한 모든 기본 C/Objective-C 타입의 자체 버전을 제공합니다. 또한, Array, Set, Dictionary라는 3가지 기본 Collection 타입을 제공합니다. Collection 타입은 추후에 알아볼 예정입니다.

 

C와 마찬가지로 Swift에서도 변수를 사용하여 값을 저장하고 이름으로 참조합니다. 또한, 값을 변경할 수 없는 변수(즉, 상수)를 광범위하게 사용하는데, C의 상수보다 훨씬 더 강력하다고 합니다.

 

C에서 볼 수 없는 튜플(tuple) 타입도 지원합니다. 아마 파이썬을 잘 알고계시다면 익숙하실 겁니다. (C++에서는 tuple을 지원합니다.)

 

Swift에서 값의 부재(absence)를 처리하기 위해서 Optional 타입을 도입했습니다. 대강 훑어봤을 때, 저한테는 확 와닿지는 않는 부분이었습니다. C에서 포인터를 NULL로 사용하는 것과 유사한 것 같지만, swift에서는 다른 모든 타입에서 Optional을 사용할 수 있습니다. Swift의 가장 강력한 기능 중 하나인데, 이것도 나중에 자세하게 한 번 살펴봐야겠습니다.

 

Swift는 type-safe 언어라고 합니다. 이는 사용자가 코드를 작성할 때 사용하는 값의 타입을 명확하게 하도록 해줍니다. 따라서 String 타입이 필요한 경우에 Int형이 실수로 전달되는 것을 방지할 수 있습니다. Type safety는 개발 과정에서 에러를 최대한 빨리 발견하고 수정할 수 있도록 도와줍니다.

 

간단하게 공식 문서에서 설명하고 있는 Swift에 관한 내용을 살펴봤습니다.

졸리는 글들은 빨리 넘어가고.. 바로 기본 문법에 대해서 알아보겠습니다.


Contants and Variables (상수와 변수)

상수/변수 선언

상수와 변수는 사용되기 전에 선언(declare)되어야 합니다. 상수는 let 키워드로 선언하고, 변수는 var 키워드로 선언합니다. 아시다시피 상수는 한 번 초기화되면 변경될 수 없지만, 변수는 계속 값이 변경될 수 있습니다.

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

위 예시에서는 최대 허용되는 로그인 횟수(maximumNumberOfLoginAttemps)는 변경되지 않아야 하기 때문에 상수로 선언되었고, 로그인 시도를 실패할 때마다 현재 로그인 시도 횟수(currentLoginAttemp)는 증가해야하기 때문에 변수로 선언되었습니다.

 

여러개의 상수 또는 변수를 쉼표로 구분하여 한 줄에 선언할 수도 있습니다.

var x = 0.0, y = 0.0, z = 0.0
Swift에서는 코드에 저장된 값이 변경되지 않으면 항상 let 키워드를 사용하여 상수로 선언하는 것을 권장합니다.

 

타입 어노테이션(Type Annotation)

type annotation을 통해 변수나 상수를 선언할 때, 해당 변수나 상수에 저장되는 값의 타입을 명확하게 지정할 수 있습니다. 변수나 상수 이름 뒤에 콜론(:)과 사용할 타입 이름을 차례로 배치하여 타입 어노테이션을 작성할 수 있습니다.

아래 코드는 변수가 문자열(String) 값을 저장할 수 있음을 나타내는 타입 어노테이션을 보여주고 있습니다.

var welcomeMessage: String
welcomeMessage = "Hello"
welcomeMessage = 10 // error

 

같은 타입의 여러 변수들을 한 줄에 정의할 수도 있습니다.

var red, green, blue: Double

이 경우 red, green, blue은 모두 Double 타입으로 정의됩니다.

 

상수/변수 네이밍

상수와 변수 이름에는 유니코드를 포함한 어떤 문자도 포함될 수 있습니다.

let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"

놀랍게도 위와 같은 이름이 변수도 가능합니다.. !

 

다만, 아래 문자는 사용할 수 없습니다. 

공백문자와 dash를 제외하고는 유니코드에서 존재하는 문자들을 말하는 것 같습니다.

또한, 변수/상수의 이름은 숫자로 시작할 수 없습니다.

 

특정 유형의 상수나 변수를 선언한 후에는 동일한 이름으로 다시 선언하거나 다른 유형의 값을 저장하도록 변경할 수 없습니다. 물론 상수를 변수로, 변수를 상수로 변경할 수도 없습니다.

 

변수와 상수 출력(print)

현재 변수나 상수의 값은 print(_:separator:terminator) 함수를 사용하여 출력할 수 있습니다.

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
print(friendlyWelcome) // Prints "Bonjour!"

print 함수는 한 개 이상의 변수를 적절하게 출력할 수 있습니다.

separator와 terminator는 기본값(default)가 있어서 생략 가능합니다. separtor는 기본값으로 " "이고, terminator는 "\n"입니다.

separator는 print 함수의 여러 개의 변수가 주어졌을 때, 변수들 사이에 삽입되는 문자열입니다.

var str1 = "abc"
var str2 = "cde"

print(str1, str2, separator:";;")
// Print abc;;cde

terminator는 출력 후, 마지막에 삽입되는 문자열입니다. 기본값이 "\n"이기 때문에 출력 후 줄바뀜이 있었는데, 아래 코드에서 str1을 출력 후에는 Tab이 삽입되어 줄바뀜이 일어나지 않습니다.

var str1 = "abc"
var str2 = "cde"

print(str1, terminator:"\t")
print(str2)
// Print abc    cde

 

Swift는 string interpolation을 사용하여 긴 문자열에 변수나 상수의 이름을 placeholder로 포함하여 Swift가 이를 현재 변수나 상수의 값으로 교체하도록 요청합니다. 백슬래시(\) 뒤에 괄호()로 변수 이름을 묶어서 사용합니다.

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

주석 (Comment)

다른 언어들과 마찬가지로 주석을 사용하여 코드에서 실행할 수 없는 텍스트를 포함시킬 수 있습니다. 당연히 주석은 컴파일할 때 무시됩니다.

Swift의 주석은 C와 매우 유사합니다. 한 줄 주석은 두 개의 슬래시(//)로 시작합니다.

// This is a comment.

여러 줄의 주석의 시작은 '/*'로 시작하며, 끝은 '*/'로 끝나야 합니다.

/* This is also a comment

but is written over multiple lines. */

C와는 다르게, Swift에서는 여러 줄을 위한 주석을 중첩하여 사용할 수 있습니다. 

/* 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. */

세미콜론 (Semicolons)

Swift에서는 각 구문 끝에 세미콜론(;)을 추가할 필요가 없습니다. 다만, 여러 개의 구문을 한 줄에 작성하기 위해서 세미콜론을 사용할 수는 있습니다.

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

Integers (정수)

정수는 42나 -23처럼 분수 성분이 없는 수 입니다. 잘 아시겠지만, 정수는 signed(+,-,0)이거나 unsigned(+,0) 중의 하나입니다. Swift에서는 8, 16, 32, 64 비트 형식의 signed 및 unsigned 정수를 지원합니다. 이 정수들은 C와 유사하게 네이밍되어 있는데, 8비트의 unsigned 정수는 UInt8이고 32비트의 signed 정수는 Int32 입니다.

 

정수 범위 (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는 현재 플랫폼의 native word 크기와 동일한 크기의 정수 타입인 Int를 제공합니다. 32비트 플랫폼인 경우에 Int는 Int32와 같은 크기를 가지고, 64비트 플랫폼에서는 Int64와 같은 크기입니다.

특정 크기의 정수로 작업해야 하는 경우가 아니면 코드의 정수 값은 항상 Int를 사용하는 것을 권장합니다. 이를 통해 코드 일관성을 높일 수 있습니다.

 

UInt

Swift는 또한 unsigned 정수 타입인 UInt도 제공합니다. Int와 마찬가지로 현재 플랫폼의 native word의 크기와 같습니다. (32비트 플랫폼에서는 UInt32의 크기와 같고, 64비트 플랫폼에서는 UInt64의 크기와 같습니다.)


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

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

부동소수점 타입은 정수형보다 훨씬 더 넓은 범위의 값을 표현할 수 있습니다. Swift에서는 두 종류의 signed 부동소수점 타입을 지원합니다.

  • Double : 64-bit 부동소수점
  • Float : 32-bit 부동소수점

Type Safety and Type Inference

글의 처음에서 언급했듯이 swift는 type-safe 언어입니다.

그렇기 때문에 코드를 컴파일할 때, 타입 검사(Type Check)를 수행하고 일치하지 않는 타입은 에러를 발생시킵니다. 따라서 개발 프로세스에서 에러를 조기에 발견하고 수정할 수 있습니다.

 

타입 검사(Type Check)는 다른 타입의 값을 사용할 때 에러를 방지하는데 유용합니다. 다만, 선언하는 모든 상수 및 변수의 타입을 지정할 필요는 없습니다. 필요한 값의 유형을 지정하지 않으면 Swift는 타입 추론(Type Inference)를 사용하여 적절한 타입으로 작업합니다. 타입 추론은 컴파일러가 코드에서 제공되는 값들을 검사하여 컴파일 할 때 타입을 자동으로 추론할 수 있도록 합니다.

 

타입 추론은 상수 또는 변수를 초기값과 함께 선언할 때 특히 유용합니다. 타입 추론은 보통 리터럴(literal)을 선언하는 지점에서 상수 또는 변수에 할당되어 수행되는데, 예를 들어, 42의 리터럴 값을 어떤 타입인지 지정하지 않고 상수에 할당하면 Swift는 상수를 정수처럼 보이는 숫자로 초기화했기 때문에 Int로 지정합니다.

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

마찬가지로, 부동소수점 리터럴의 타입을 지정하지 않아도, Swift는 Double 형으로 추론하여 타입을 지정합니다.

let pi = 3.14159
// pi is inferred to be of type Double
부동소수점 타입을 추론할 때, 항상 Float 대신 Double로 추론합니다.

만약 정수와 부동소수점 리터럴이 결합되는 형태라면 문맥상 Double 타입으로 추론됩니다.

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

Numeric Literals (숫자 리터럴)

정수 리터럴은 다음과 같이 작성될 수 있습니다.

  • 10진수 : no prefix
  • 2진수 : 0bXX
  • 8진수 : 0oXX
  • 16진수 : 0xXX

10진수 17을 다양한 진법으로 표현하면,

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

위와 같습니다.

 

부동소수점 리터럴은 접두사없이 10진수로 표현되거나 0x 접두사를 포함하여 16진수로 표현될 수 있습니다. 10진수 부동소수점에는 (optional) 지수가 있어야 하고 이는 대문자나 소문자 e로 나타냅니다. 16진수에는 지수가 반드시 있어야하며, 대문자나 소문자 p로 나타냅니다.

exp 지수가 있는 10진수는 base number에 10\(^exp\)를 곱합니다.

  • 1.25e2는 \(1.25 \times 10^2\), 또는 125.0을 의미합니다.
  • 1.25e-2는 \(1.25 \times 10^{-2}\), 또는 0.0125를 의미합니다.

16진수의 경우에는 \(2^{exp}\)를 곱하고, 아래처럼 나타냅니다.

  • 0xFp2는 \(15 \times 2^2\), 또는 60.0을 의미합니다.
  • 0xFp-2는 \(15 \times 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

Numeric Type Conversion (숫자 타입 변환)

 

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

첫 번째 코드는 UInt8 타입에 음수를 저장했기 때문에 에러가 발생합니다.

두 번째 코드는 Int8의 최대값에 1을 더하면 256이 되어 Int8에서 정의된 범위에 맞지 않으므로 에러가 발생합니다. C와 같은 언어처럼 오버플로우가 발생하는 것이 아닌 아예 에러를 발생시키는게 조금 다르네요.. !

 

이처럼 숫자 타입마다 서로 다른 범위의 값을 저장할 수 있으므로 숫자 타입 변환을 위해서 case-by-case로 선택해야합니다. 이러한 접근 방법은 숨겨진 변환 에러를 방지하고, 타입 변환의 의도를 코드에 명시하는데 도움이 됩니다.

 

특정 숫자 타입을 다른 타입으로 변환하려면 원하는 타입을 기존값으로 초기화합니다. 아래 코드에서는 상수 twoThousand는 UInt16 타입이지만, 상수 one은 UInt8 타입입니다. 같은 타입이 아니라서 직접 더할 수는 없지만, 대신 아래 코드에서는 one의 값으로 초기화된 UInt16를 새로 만들어 사용합니다.

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

 

SomeType(OfInitialValue)는 Swift 타입의 Initializer를 호출하고 초기값을 전달하는 기본적인 방법입니다. 위 코드에서 UInt16은 Initializer를 가지고 있고, 이 Initializer는 UInt8의 값을 읽습니다. 그리고 새로운 UInt16 타입을 만듭니다. 그러나 아무 타입이나 Initializer에 전달할 수는 없습니다. 이에 관해서는 추후에 Extensions에서 다루도록 하겠습니다.

 

Integer and Floating-Point Conversion

정수와 부동소수점 타입 간의 변환은 반드시 명시적으로 수행되어야 합니다.

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

상수 three의 값은 Double 타입의 새로운 값으로 만들기 위해 사용되어서, 같은 타입으로 덧셈이 수행되도록 합니다. 이러한 명시적인 변환이 없다면, 덧셈은 수행되지 않고 에러를 발생시킵니다. Double(three)를 그냥 three로 두고 덧셈을 수행하도록 실행해보면, 다음의 에러가 발생합니다.

부동소수점에서 정수로의 변환도 명시적이어야 합니다. 정수 타입은 Double이나 Float 값으로 초기화될 수 있습니다.

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

이런 방식으로 부동소수점 값을 새로운 정수 값으로 초기화할 때, 부동소수점의 값은 항상 잘립니다. 4.75는 4가 되고, -3.9는 -3이 됩니다.


Type Aliases

Type aliases(타입 별칭)은 존재하는 타입의 다른 이름을 정의합니다. 이는 typealias 키워드를 통해서 type aliases을 정의할 수 있습니다.

Type aliases는 기존 타입을 상황에 맞는 이름으로 참조하려는 경우에 유용하게 사용할 수 있습니다. 

typealias AudioSample = UInt16

Type alias를 정의하면, 원래 이름을 사용할 수 있는 모든 위치에서 이를 사용할 수 있습니다.

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

AudioSample은 UInt16의 alias로 정의되었기 때문에 AudioSample.min은 실제로 UInt16.min입니다.


Booleans

Swift는 bool이라는 기본 boolean 타입을 지원합니다. Boolean 값은 오직 true나 false가 됩니다.

let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrange와 turnipsAreDelicious의 타입은 Bool로 추론되는데, 이는 Boolean 리터럴 값으로 초기화되었기 때문입니다. 

Boolean 값은 if문과 같은 조건문을 사용할 때 유용합니다.

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

 

Swift의 type safety는 Boolean이 아닌 값이 Bool로 사용되는 것을 방지합니다. 따라서 아래 코드는 컴파일할 때 에러가 발생합니다. 다른 언어와는 차별된 부분이네요.

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
}

i == 1 비교의 결과의 타입은 Bool 이기 때문입니다.


Tuples 튜플

튜플(Tuples)는 여러 개의 값을 하나의 compound value(복합값)으로 그룹화합니다. 튜플 내의 값들은 어떤 타입이든지 될 수 있고 서로가 같은 타입일 필요가 없습니다.

아래 예제에서 (404, "Not Found")는 HTTP의 status code를 의미하는 튜플입니다.

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

(404, "Not Found") 튜플은 Int와 String을 함께 묶어 그룹화합니다. http404Error 튜플은 (Int, String) 타입의 튜플이라고 설명할 수 있습니다.

 

모든 타입의 순열에서 튜플을 생성할 수 있고, 원하는 만큼 다양항 타입을 포함할 수 있습니다. (Int, Int, Int), (String, Bool) 등 다양한 순열의 타입을 가진 튜플을 생성할 수 있습니다.

 

존재하는 튜플의 contents를 각각의 상수나 변수로 분해(decompose)할 수도 있습니다. 각각의 상수나 변수는 아래처럼 액세스할 수 있습니다.

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"

만약 튜플 값의 일부만 필요하다면 필요없는 부분은 underscore(_)를 사용해서 무시할 수 있습니다. 이는 파이썬과 동일하네요.

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) 튜플 타입을 반환할 수 있습니다. 각각 다른 타입을 가진 두 개의 값을 갖는 튜플을 반환함으로써 함수는 단일 타입의 단일 값만 반환할 수 있는 경우보다 더 유용한 정보를 제공합니다.


Optional

값이 없을 수 있는 경우에 optionals를 사용합니다. Optional은 다음의 두 가지 가능성을 표현합니다. 

  • 값이 존재하는 경우, 이 값에 액세스하기 위해서는 unwrap을 할 수 있음
  • 값이 존재하지 않는 경우
이 개념은 C나 Objective-C에는 존재하지 않습니다. 포인터나 객체에 0이나 NULL이 그나마 유사한 개념인 것 같습니다. 하지만 Swift에서는 어떠한 타입에도 optional을 사용할 수 있습니다.

아래 코드를 살펴보겠습니다.

Swift의 Int 타입에는 문자열 값을 Int값으로 변환하는 Initializer가 있습니다. 하지만 모든 문자열을 정수로 변환할 수는 없습니다. "123"은 숫자 123으로 변환할 수 있지만, "hello, world"는 변환할 수 있는 명확한 숫자 값이 없습니다.

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

따라서, String 값을 Int로 변환하는 Initializer는 변환할 수 없는 문자열 값을 받을 수 있으므로, Int가 아닌 Optional Int 타입을 반환합니다. Optional Int는 Int?로 사용됩니다. 여기서 물음표는 해당 값이 Optional이라는 것을 나타냅니다. 따라서, Int값이 포함될 수도 있고 아무런 값도 없을 수도 있다는 것을 의미합니다.

let possibleNumber = "hello, world"
let convertedNumber = Int(possibleNumber)
dump(convertedNumber)

위 코드를 실행하면, convertedNumber가 nil로 출력됩니다. dump는 인스턴스의 자세한 설명을 출력하는 함수입니다.

nil

특별한 값 nil을 할당하여, optional 변수를 값이 없는 상태로 설정할 수 있습니다.

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value
nil을 optional이 아닌 상수나 변수에 사용할 수 없습니다.

만약 optional 변수를 초기값없이 정의하면, 이 변수는 자동으로 nil로 초기화됩니다.

var surveyAnswer: String?
// surveyAnswer is automatically set to nil

 

If Statements and Forced Unwrapping

if문에서 optional 변수와 nil을 비교하여 optional 변수가 값을 포함하고 있는지 확인할 수 있습니다. 이 비교는 equal to 연산자(==)나 not equal to 연산자(!=)를 사용하면 됩니다.

만약 optional 변수가 값을 가지고 있다면 이는 'not equal to' nil 로 간주됩니다.

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

 

Optional 변수에 값이 있음을 확신한다면, Optional 변수 이름 끝에 느낌표(!)를 추가하여 이 값을 액세스할 수 있습니다. 느낌표는 '이 Optional 변수가 분명히 값을 가지고 있다는 것을 알고 있으니 이를 사용하라'라는 의미입니다. 이를 forced unwrapping이라고 합니다.

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."

 

Optional Binding

Optional Binding을 사용하여 Optional 변수가 값을 가지고 있는지 확인하고, 만약 가지고 있다면 해당 값을 임시 상수나 변수로 사용할 수 있도록 할 수 있습니다. Optional Binding은 if 또는 while문과 함께 사용하여 Optional 내부에서 값을 체크하고 상수나 변수로 그 값을 추출할 수 있습니다. 더욱 자세한 내용은 Control Flow에서 더 알아보겠습니다.

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"

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

"만약 Int(possibleNumber)에 의해서 반환된 Optional Int가 값을 포함하고 있다면, 이 값을 새로운 상수 actualNumber에 할당하라"

 

만약 변환이 성공적이라면 actualNumber 상수는 if문의 첫번째 브랜치에서 사용할 수 있을 것입니다. actualNumber는 Optional 변수의 값으로 이미 초기화되었기 때문에 더이상 forced unwrapping을 위한 느낌표(!)를 사용하지 않아도 됩니다.

Optional Binding은 상수뿐만 아니라 변수를 사용할 수도 있습니다. if문의 첫번째 브랜치 내에서 actualNumber의 값을 바꾸려는 경우 var actualNumber로 사용하면 됩니다.

 

쉼표(,)로 구분하여 필요한 수의 Optional Binding이나 Boolean 조건을 if문에 포함할 수 있습니다. 만약 Optional Binding 값 중의 하나가 nil이거나 Boolean 조건이 false라면 if문의 조건은 false로 간주됩니다.

아래의 두 if문은 동일한 표현입니다.

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"

 

Implicitly Unwrapped Optionals

위에서 설명한 대로, Optional은 상수나 변수가 값이 없음을 허용한다는 것을 나타냅니다. Optional은 if문을 사용하여 값이 존재하는지 확인할 수 있으며, Optional Binding이 있는 경우 Optional 변수의 값이 있으면 조건부로 unwrapping하여 Optional 값에 액세스할 수 있습니다.

때때로 프로그램 구조에서 값이 처음 설정되고 나면 Optional이 항상 값을 갖는다는 것이 명확해집니다. 이런 경우에 Optional 값은 항상 값이 존재한다고 가정할 수 있으므로 액세스할 때마다 체크하고 unwrap할 필요가 없습니다.

 

이런 종류의 Optional들은 implicitly unwrapped optionals로 정의됩니다. implicitly unwrapped optionals는 물음표(String?) 대신 느낌표(String!)를 붙여서 사용할 수 있습니다. 이 Optional은 처음 정의된 이후 시점에 값이 존재한다고 확인될 때 유용합니다. Swift에서 이 Optional들은 주로 클래스 초기화 동안 사용됩니다.

 

살펴보면 implicitly unwrapped optional도 보통의 optional 이지만, 액세스할 때마다 optional 값을 unwrapping할 필요없는 non-optional처럼 사용할 수 있습니다. 아래 코드는 Optional String과 Implicitly Unwrapped Optional String의 차이를 보여줍니다.

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires 느낌표

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for 느낌표

implicitly unwrapped optional은 필요할 때 force-unwrapped의 권한을 optional에 준 것으로 생각할 수 있습니다. implicitly unwrapped optional 값을 사용할 때, Swift는 처음에 이를 평범한 optional 값으로 사용하려고 하며, optional로 사용할 수 없으면 Swift는 강제로 값을 unwrap합니다. 

 

아래 코드에서 optionalString은 명시적 타입이 없으므로 보통의 Optional 타입으로 추론됩니다.

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

만약 implicitly unwrapped optional의 값이 nil이고 이 optional의 wrapped value값에 액세스하려고 하면 런타임 에러가 발생합니다. 이 결과는 일반 Optional 뒤에 느낌표를 붙여서 nil과 비교하는 것과 동일합니다.

 

일반 Optional과 동일하게 implicitly unwrapped optional도 nil인지 아닌지 확인할 수 있습니다.

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

그리고 implicitly unwrapped optional도 Optional Binding을 사용하여 값을 체크하고 값을 unwrap하여 사용할 수 있습니다.

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

Error Handling

프로그램을 수행하는 동안 발생할 수 있는 에러 조건에 대응하기 위해 Error Handling을 사용할 수 있습니다. Error Handling은 후에 다시 자세하게 알아보겠습니다.

 


Assertions and Preconditions

Assertions과 Preconditions는 런타임에 수행되는 검사입니다. 나머지 코드를 실행하기 전에 필수 조건을 만조교했는지 확인하기 위해서 이러한 코드를 사용합니다. Assertions나 Preconditions의 Boolean 조건이 true라면 코드는 계속해서 수행되고, false라면 프로그램의 상태가 유효하지 않는 것을 의미하고 코드 실행이 중단되고 프로그램이 종료됩니다.

이들은 코드에 한 부분으로 포함될 수 있는데, Assertions은 개발 과정 중에 실수나 잘못된 가정을 찾는데 도움이 되고, Preconditions는 Production에서 발생하는 문제를 발견하는데 도움이 됩니다.

이러한 기능을 사용하여 유효한 데이터와 상태가 되도록 강제하면 프로그램이 유효하지 않은 상황이 발생했을 때 쉽게 종료되고 디버깅하는데 도움이 됩니다.

 

Assertions과 Precondition의 차이는 언제 체크되느냐입니다. Assertions은 오직 디버그 빌드에서만 활성화되어 검사하는데, Preconditions는 디버그와 프로덕션 빌드 모두에서 활성화되어 검사합니다.

Debuggin with Assertions

Swift Standard Library의 assert(_:_:file:line:) 함수를 호출하여 Assertions을 작성할 수 있습니다.

첫 번째에 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.

위 코드에서 age >= 0이 true로 평가될 경우, 프로그램은 계속해서 수행됩니다. 위 코드는 false이기 때문에 Assertions이 실패하고 프로그램이 종료됩니다.

 

전달되는 메세지는 생략할 수 있습니다. 메세지의 기본값은 빈 문자열입니다.

assert(age >= 0)

만약 코드에서 이미 조건을 체크했다면, assertionFailure(_:file:line:) 함수를 사용하여 assertion이 실패했다는 것을 나타낼 수 있습니다.

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 Preconditions

조건이 잠재적으로 false일 수 있는 상황에서 Precondition을 사용하지만, 코드가 계속해서 실행되려면 반드시 true이어야 합니다. precondition(_:_:file:line:) 함수를 호출하여 작성할 수 있습니다. assertion과 동일하게 true 또는 false로 평가되는 조건과 false일 경우 출력할 메세지를 함수의 인자로 전달합니다.

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

마찬가지로 preconditionFailure(_:file:line:) 함수를 호출하여 실패가 발생했다는 것을 나타낼 수 있습니다.

 


Swift의 기본 문법들을 속성으로 한 번 훑어본 느낌이네요. 다음부터는 Swift 언어를 조금 더 세부적으로 살펴보도록 하겠습니다.

댓글