공식 문서로 공부하는 Swift (0) - The Basics

ci·2020년 5월 25일
3

Swift 공식 문서한글 번역을 정리하고 공부합니다.

The Basics

Declaring Constants and Variables

상수 : let 한 번 값을 할당하면 바꿀 수 없다.

변수 : var 값을 변경할 수 있다.

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

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

한 줄에서 다수의 상수 또는 변수를 선언할 수 있다.



Type Annotations

변수나 상수를 선언할 때, type annotation을 통해 타입을 지정할 수 있다. 이는 상수 또는 변수가 어떤 타입을 저장할 수 있는지를 명시적으로 보여준다.

var welcomeMessage: String
welcomeMessage = "Hello"

var red, green, blue: Double

"welcomeMessage이라는 변수를 String 타입으로 선언한다. "

타입이 모두 같다면, 한 줄에서 다수의 상수 또는 변수의 타입을 지정할 수 있다.

Swift는 타입 추론을 제공하기 때문에 type annotation을 명시하지 않았다면 최초 값을 토대로 타입을 추론하게 된다.



Naming Constants and Variable

let π = 3.14159
let 상수 = "상수입니다."
let 🐶🐮 = "dogcow"

상수와 변수 이름은 유니코드에서 지원하는 대부분의 문자를 포함할 수 있다.

다음의 경우는 허용하지 않는다.

공백이 포함된 이름

숫자로 시작하는 이름

연산자로 사용될 수 있는 기호 및 화살표

Swift에서 사용하고 있는 예약어 또는 키워드

┌와 같은 box-drawing characters

│와 같은 line-drawing characters

해당 코드 범위 내에서 이미 사용하고 있는 이름


한 번 상수와 변수의 타입을 선언했다면,

같은 이름으로 재정의할 수 없다.

다른 타입의 값을 저장할 수 없다.

상수를 변수로 바꾸거나, 변수를 상수로 바꿀 수 없다.


기존에 존재하는 변수를 호환되는 타입의 값으로 변경할 수 있다.

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome is now "Bonjour!"

상수를 변경하려고 할 시, 컴파일 과정에서 에러가 발생한다.

let languageName = "Swift"
languageName = "Swift++"
// This is a compile-time error: languageName cannot be changed.


Printing Constants and Variables

print(_:separator:terminator:) 을 사용해 상수 혹은 변수를 출력할 수 있다.

print 함수는 그것의 결과물을 XCode의 console 에 출력한다. seperatorterminator 인자에는 기본값이 존재하기 때문에 생략이 가능하다.

출력하면서 줄을 넘기고 싶지 않다면 print(someValue, terminator: "")와 같은 형태로 terminator 에 값을 할당한다.


Swift는 문자열 보간(string interpolation)을 사용할 수 있다.

String에 placeholder로서 상수 혹은 변수 이름을 포함하는 방식으로 작동한다.

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


Semicolons

Swift는 세미콜론(;)을 필요로 하지 않는다. 하지만 한 줄에 여러 구문을 작성하고자 할 경우에는 세미콜론으로 각각을 분리해야 한다.

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


Integers

Swift에서 정수(Integers)는 양의 정수, 0, 음의 정수를 모두 포함하는 signed 와 양의 정수, 0만을 포함하는 unsigned로 나뉘어진다.

Swift는 8, 16, 32, 64 비트의 정수 형태를 제공한다.

UInt8 : 8-bit unsigned integer

Int32 : 32-bit signed integer


다음과 같이 각 정수 타입의 최솟값과 최댓값에 접근할 수 있다.

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

일반적으로는 Integer의 사이즈를 지정할 필요가 없다. Int 타입은 32비트 플랫폼에서는 Int32로, 64비트 플랫폼에서는 Int64로 설정된다.

UInt 역시 같은 방법으로 사이즈가 결정된다. UInt는 특별히 unsigned integer가 필요한 것이 아니면 사용하지 않는 것이 권장된다.



Floating-Point Numbers

Floating-point 타입은 integer 타입보다 더 넓은 범위의 수를 표현할 수 있다. Swift에서는 두 가지 종류의 Floating-Point Number 타입이 있다.

Double 타입은 64비트의 Floating-point number를 표현한다.

Float 타입은 32비트의 Floating-point number를 표현한다.

Double 타입은 소수점 이하 15자리, Float은 소수점 이하 6자리를 표현할 수 있다. 일반적으로 Double을 사용하는 것이 권장된다.



Type Safety and Type Inference

Swift는 type-safe 언어다. 이는 코드가 작동할 수 있도록 하는 값들의 타입을 명확하게 만들어주는 장점이 있다.

코드를 컴파일할 때 타입 체크를 실시하며, 타입이 맞지 않을 경우 에러를 발생시킨다. 개발 과정에서 빠르게 에러를 발견하고 고칠 수 있도록 해 준다.

타입 체크는 다른 타입의 값을 사용함으로써 발생하는 에러를 피할 수 있게 돕는다. 하지만 이는 선언한 모든 상수와 변수가 특정 타입만을 가져야 한다는 뜻은 아니다. 만약 값의 타입을 특정하지 않는다면, Swift는 타입 추론을 통해 적당한 타입을 찾아 적용한다. 타입 추론은 컴파일러가 코드를 컴파일할 때 특정 표현의 타입을 자동으로 추론할 수 있도록 한다.

타입 추론은 초기값과 함께 상수, 변수를 선언할 때 특히 유용하다. 이는 리터럴(literal)을 상수나 변수에 할당할 때 자주 사용된다.

let a(상수) = 1(리터럴)

상수 : 변하지 않는 변수. 참조변수를 상수로 지정할 때, 참조변수는 상수지만 그 주소가 가리키는 데이터들까지 상수라는 의미는 아니다.

리터럴 : 변수에 넣는 변하지 않는 데이터 그 자체.

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

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

만약 integerfloating-point 리터럴을 결합한다면, 그 상수/변수의 타입은Double로 추론된다.

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


Numeric Literals

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

10진수 : 접두사를 붙일 필요가 없다.

2진수 : 0b

8진수 : 0o

16진수 : 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

실수 리터럴은 10진수 혹은 16진수가 될 수 있다. 실수 리터럴은 항상 소수점 양 쪽(정수부, 소수부)의 값을 갖고 있어야 한다.

10진수 소수는 e의 거듭제곱으로 표현할 수 있다. 16진수는 e 대신 p가 사용된다.

  • 1.25e2 means 1.25 x 102, or 125.0.
  • 1.25e-2 means 1.25 x 10-2, or 0.0125.
  • 0xFp2 means 15 x 22, or 60.0.
  • 0xFp-2 means 15 x 2-2, or 3.75
// 12.1875를 표현하는 세 가지 방법
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

숫자 리터럴(Numeric literals)은 가독성을 위해 추가적인 포맷팅을 포함할 수 있다. 정수와 실수 모두 추가적인 0으로 채워지거나 언더스코어(_)을 추가하는 것이 가능하다. 이러한 포맷팅은 리터럴에 어떤 영향도 미치지 않는다.

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


Numeric Type Conversion

각각의 숫자 타입마다 정수형 상수, 변수에 저장할 수 있는 숫자의 범위가 다르다. Int8 상수, 변수의 경우 -128부터 127까지의 숫자만 저장할 수 있다. 반면 UInt8 상수, 변수는 0부터 255까지의 숫자를 저장할 수 있다. 이 범위에 맞지 않을 경우 코드가 컴파일될 때 에러가 발생한다.

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

각각의 숫자 타입이 저장할 수 있는 범위가 다르기 때문에, 상황에 맞춰 사전에 타입 변환을 실시해야 한다. 이러한 opt-in approachhidden conversion errors를 방지하고 타입 변환의 의도를 명시적으로 만든다.

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

SomeType(ofInitialValue)은 Swift 타입의 initializer를 호출하는 기본적인 방법이다. 위 사례에서 UInt16UInt8 값을 받아들일 수 있는 initializer를 갖고 있으며, 기존의 UInt8 값을 새로운 UInt16 값으로 만든다.



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

상수 threeDouble 타입의 새로운 값으로 만들어져 양쪽 값이 같아지게 된다. 이러한 변환이 없는 합연산은 허용되지 않는다.


실수에서 정수로 변환하는 것도 반드시 명시적이어야 한다. 새로운 정수로 변환되는 실수값은 항상 truncated된다. (4.75 -> 4, -3.9 -> -3)

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

상수/변수를 결합하는 것(three + pointOneFourOneFiveNine 또는 three + 0.14159)과 리터럴끼리 결합하는 것(3 + 0.14159)은 엄연히 다르다. 리터럴끼리의 결합은 에러가 발생하지 않는다. 숫자 리터럴은 명시적 타입을 갖고 있지 않으며, 컴파일 과정에서 평가되어 타입이 추론된다.



Type Aliases

Type Alias는 기존의 타입을 대체하는 이름을 typealias 키워드를 사용해 정의한다. 기존의 타입명을 문맥적으로 더 적합한 이름으로 지칭하는 데 유용하다.

typealias AudioSample = UInt16

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


Booleans

Swift의 Boolean 타입은 Bool로 칭한다. Swift는 truefalse 두 가지의 상수값을 제공한다.

let orangesAreOrange = true
let turnipsAreDelicious = false

Boolean값은 조건문과 함께할 때 특히 유용하다.

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

스위프트의 타입 세이프는 Boolean이 아닌 값이 그 역할을 대신하지 못하도록 한다.

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

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


Tuples

튜플(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"

튜플 내의 인덱스 번호를 이용해 각각의 값에 접근할 수도 있다.

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"

튜플은 함수의 리턴값으로 유용하게 쓸 수 있다. 다양한 타입으로 조합된 튜플을 사용하여 단일 타입을 반환할 때보다 더 유용한 정보를 제공하는 함수를 만드는 것이 가능하다.



Optionals

빈 값이 들어갈 수 있다면, optionals를 사용할 수 있다.

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

Swift의 Int 타입은 StringInt로 변환시켜주는 initializer를 갖고 있다. 하지만 모든 문자열이 정수로 변환되는 것은 아니다. "123"은 123으로 변환될 수 있지만, "hello, world"는 정수로 바뀔 수 없다. 이처럼 initializer가 실패할 수 있기 때문에, optional Int를 반환한다. 이는 Int?로 표현된다. 물음표는 이 상수/변수가 포함하고 있는 값이 optional이며, Int값을 포함하거나 아무것도 포함하지 않음을 뜻한다.



nil

optional 변수의 변수 없음 상태는 nil로 나타낸다.

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value

만약 optional 변수를 초기화하지 않고 선언한다면 자동으로 nil값이 저장된다.

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

Swift의 nil과 Objective-C의 nil은 다른 개념이다. Objective-C에서 nil은 존재하지 않는 오브젝트를 가리키는 포인터다. 반면 Swift에서 nil은 포인터가 아니라 특정 타입의 값이 존재하지 않음을 나타낸다. 오브젝트 타입뿐만 아니라 어떤 것이라도 nil이 될 수 있다.



If Statements and Forced Unwrapping

optionalnil과 비교하는 if 구문을 사용하여 optional이 값을 포함하고 있는지 판단할 수 있다.

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

값을 포함하고 있는 게 확실하다면, optional의 이름 뒤에 느낌표(!)를 붙여서 값에 접근할 수 있다. 느낌표는 "이 optional은 확실히 값을 가지고 있다."는 것을 뜻한다. 이를 optional value의 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 내부의 값을 체크하고, 그 값을 상수/변수로 추출한다. 이를 ifwhile 구문에 사용 가능하다.

if let constantName = someOptional {
    // statements
}

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

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

"만약 Int(possibleNumber)가 반환하는 optional Int가 값을 포함하고 있다면, actualNumber라는 새로운 상수에 이를 할당한다."

변환이 성공했다면 actualNumber는 첫 번째 조건문 안에서 사용할 수 있게 된다. 여기서는 값에 접근하기 위해 느낌표(!)를 사용할 필요가 없다.


많은 수의 optional binding과 조건을 ,로 구분하여 하나의 if 구문에 포함시킬 수 있다. optional bindingnil이거나 조건이 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"


Implicitly Unwrapped Optionals

optional이 첫 번째 값을 할당받은 이후에 항상 값을 가지고 있는 경우가 있을 것이다. 이 경우에는 optional의 값을 체크하고 unwrap할 필요를 없애고 항상 접근할 수 있도록 하는 것이 유용하다.

이러한 optionalimplicitly unwrapped optionals라 부른다. 타입의 뒤에 물음표(?) 대신 느낌표(!)를 붙임으로써 이를 표현할 수 있다.

implicitly unwrapped optionals는 첫 번째 값이 할당된 즉시 값이 존재하며, 앞으로도 계속 존재할 것임이 확실할 때 사용하면 유용하다. 클래스를 초기화 할 때 가장 잘 쓸 수 있다.

implicitly unwrapped optionals는 이면에서는 일반 optional value와 동일하지만, 접근할 때마다 unwrapping 할 필요 없이 non-optional value처럼 사용할 수 있다.

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

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

implicitly unwrapped optionals를 사용할 때 Swift는 우선 일반적인 optional값처럼 이를 사용하려고 시도한다. 위의 코드에서 assumedStringimplicitString에 할당되기 전 force-unwrap된다. implicitStringexplicit non-optional값이기 때문이다. optionalString은 타입이 명시적이지 않고, 일반적인 optional값으로 할당된다.

만약 implicitly unwrapped optionalsnil이고 여기에 접근하려 시도한다면 런타임 에러가 발생한다.


implicitly unwrapped optionals 역시 nil 체크와 optional binding이 가능하다.

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

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


Error Handling

실행 중 에러가 발생했을 때 대응하기 위해 error handling을 사용한다.

함수는 에러를 만났을 때 그것을 던진다(throws). 이를 잡아서(catch) 적절히 처리할 수 있다.

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

throws 키워드를 통해 이 함수는 에러를 던질 수 있음을 알려준다. 이와 같이 에러를 던질 수 있는 함수를 호출할 때, try 키워드를 표시해야 한다.


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)
}


Assertions and Preconditions

assertionspreconditions는 런타임 시에 발생하는 체크다. 코드를 실행하기 전에 핵심적인 상태를 만족하는지 확인하고자 사용한다. assertionspreconditions 안의 Boolean 조건이 true라면 코드가 실행되지만, false라면 코드 실행이 멈추고 앱이 종료된다.

코드 작성 중 세운 가설이나 예상을 표현하기위해 assertionspreconditions을 사용한다. 때문에 이 안에 코드의 일부분을 포함시킬 수 있다. assertions는 실수와 개발 중의 잘못된 가정을 찾는 데 도움을 주며, preconditions는 제품의 이슈를 발견하는 데 도움을 준다.

에러 처리와는 다르게, assertionspreconditions는 예상되거나 회복 가능한 에러를 사용하지 않는다. assertionspreconditions의 실패는 곧 프로그램 상태가 무효임을 나타내며, 이를 잡을 방법이 없기 때문이다.

assertionspreconditions의 차이는 체크가 되는 시기이다.

assertion : 디버그 빌드에서만.

preconditions : 디버그와 배포 빌드 둘 다.


assert(_:_:file:line:) 함수를 호출함으로써 assertion을 작성할 수 있다. truefalse를 판별할 표현식과 false일 때 나타낼 메시지를 전달한다. 메시지는 생략 가능하다.

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

만약 코드가 이미 조건을 체크했다면, assertionsFailure(_: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.")
}

코드가 실행되려면 반드시 참이어야 하지만 잠재적으로 실패할 수 있는 조건에 precondition을 사용한다.

precondition(_:_:file:line:) 함수를 호출하여 precondition를 사용한다. truefalse를 판별할 표현식과 false일 때 나타낼 메시지를 전달한다. 메시지는 생략 가능하다.

또한 실패가 발생했음을 나타내는 preconditionFailure(_:file:line:)을 호출할 수도 있다.

unchecked mode(-0unchecked)에서 컴파일 한다면 precondition은 작동되지 않는다. 컴파일러는 항상 참이라고 가정한다. 하지만 fatalError(_:file:line:) 함수는 최적화 세팅과 무관하게 실행을 중단한다.

프로토타이핑과 초기 개발 단계에서 fatalError(_:file:line:)를 사용할 수 있다.

1개의 댓글

comment-user-thumbnail
2022년 2월 27일

감자합니다 잘 참고하겠습니다

답글 달기