Swift study - The Basics

rbw·2022년 2월 19일
0

swift-study

목록 보기
5/17
post-thumbnail

Swift - The Bascis

원문을 보며 해석을 해보면서 써내려 가려고 한다

스위프트는 C언어와 같게 변수를 사용하여 값을 저장하고 참조한다. 그리고 우리가 잘 아는 상수와 같은 역할을 하는 값을 사용하는데 스위프트에서는 좀 더 강력한 기능이 있다. 상수는 코드를 더 안전하고 명확하게 만들기 위해 스위프트 전체에서 사용된다.

스위프트는 Objective-C 에서는 없는 tuple을 제공하고, 이는 그룹 값을 만들고 이를 전달할 수 있다. tuple을 사용하여 함수의 여러 값들을 단일 복합 값으로 반환이 가능하다

스위프트는 또한 optional type을 제공하고, 이는 값이 없을때 유용하게 사용된다 이것을 사용하는 것은 Objetive-C에서 포인터와 함께 nil 을 사용하는 것과 비슷하지만 모든 타입에서 사용이 가능하다는 점이 다르다. 이 기능은 Swift에서 매우 중요한 특징 중 하나이다

스위프트는 type-safe language로서, 언어가 코드에서 사용하는 값에 대해 타입을 명확하게 해주는 것을 도와준다. 만약 String을 요구하는 경우에 Int 타입을 전달하려고 하면 에러를 띄워준다. 비슷하게 type-safetyoptional Stringnon-optional String 에 전달하는 것을 막아준다. 이 기능은 개발과정에서 코드의 에러를 좀 더 빠르게 캐치하고 고칠 수 있게끔 도와준다.

Constants and Variables (상수와 변수)

상수와 변수는 특정 타입의 값을 이름과 연관지어서 사용한다. 상수는 한 번 정하면 더 이상 값을 변경하지 못한다. 반면에 변수는 언제든 바꿀 수가 있다

Declaring Constants and Variables (상수와 변수 선언)

상수와 변수는 사용하기전에 반드시 선언되어야 한다 상수는 let 키워드와 같이 선언하고, 변수는 var 키워드를 사용한다

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

// 이런 식으로 여러개의 상수나 변수를 콤마를 사용해서,
// 하나의 라인으로 선언이 가능하다
var x = 0.0, y = 0.0, z = 0.0

저장된 값이 변경되지 않는다면 상수를, 변경할 필요가 있는 값은 변수로 선언해라

Type Annotations (타입 명시(주석?))

여러 종류의 상수, 변수 값에 대해 명확하게 하기 위해서 상수와 변수를 선언할 때 타입을 명시 할 수 있다. 다음과 같이 사용한다

var welcomeMessage: String

// 이제 오류없이 어떤 문자열이든 지정할 수 있다.
welcomeMessage = "Hello"

// 여러 값들을 타입지정하는 방법
var red, green, blue: Double

위 코드는 welcomeMessage가 문자열이라는것을 보여준다. :의 의미는 ...의 타입은... 이라는 뜻이다. 위 코드에서는 모든 문자열 타입을 저장할수 있다는 의미이다.

이것의 의미를 저장할 수 있는 어떤 타입(or 어떤 종류)라고 생각해라

타입 명시를 사용하는 경우는 드물다. 스위프트는 초기값을 지정하면 거의 추론이 가능하기 때문이다.

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

Unicode를 포함하여 거의 모든 글자를 사용하여 지정할 수 있다.

let π = 3.14159
let 🐶🐮 = "dogcow"

상수와 변수의 이름에는 공백, 수학기호, 화살표, 개인전용 유니코드 스칼라 값, 또는 선과 박스 그림 문자를 사용할 수 없습니다. 또 숫자가 다른 곳에서는 사용이 될 수 있지만, 시작 첫글자로는 사용이 불가하다

또 한번 선언을 하였다면, 다른 타입의 값으로 변경하거나, 같은 이름으로 선언이 불가하다. 그리고 상수를 변수로, 변수를 상수로 변경하는 것도 불가하다

만약에 같은이름으로 선언을 해야한다면 ` 기호를 사용하여 묶으면 되지만, 되도록 사용하지 않는것이 좋다.

스위프트에서 값을 변경하는 예제

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:) function 를 사용해서 출력이 가능하다

print(friendlyWelcome)
// Prints "Bonjour!"

print 함수는 전역함수로, 하나 이상의 값을 적절히 출력한다 separator와 terminator 파라미터는 기본 값을 가지고 있어 호출시에 생략이 가능하다. 기본적으로 줄 바꿈을 출력하고 종료된다. 줄 바꿈 없이 값을 출력하려면, terminator 파라미터에 빈 문자열을 추가하면 된다. print(someValue, terminator: "")

스위프트는 문자열 보간법을 사용하여서 긴 문자열을 대신하는 상수나 변수의 이름을 적을 수 있다. 그리고 상수나 변수의 현재값을 대체하려고 시도한다. 사용법은 다음과 같다

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

Comments (주석)

스위프트는 실행이 불가능한 텍스트를 사용이 가능하다 주석이라고 부르며, 이는 스위프트 컴파일러가 컴파일 할 때 무시한다 C언어와 사용이 거의 비슷하다

C와 사용법이 거의 비슷
// This is a comment.

C와는 다르게 주석안에 주석을 중첩 가능하다
/* 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(;)

다른언어와 달리 스위프트에서는 세미콜론(;)을 사용하지 않는다. 그러나 한 줄의 여러개의 문장을 작성할 경우에는 필요하다.

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

Integers (정수)

정수는 분수 요소를 뺀 모든 숫자를 의미한다 42, -23 등. 정수는 부호가 있거나(양수, 0, 음수) 없다(양수, 0)

스위프트는 8, 16, 32, 64비트의 부호가 있거나 없는 정수를 제공한다. 이 정수들은 C와 비슷하게 이름을 짓고 부호가 없는 8비트 정수의 경우 UInt8로, 부호가 있는 32비트 정수는 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

대부분의 경우 특정 크기의 정수를 선택할 필요는 없다. 스위프트는 추가적인 정수 타입을 제공하는데, 32bit, 64bit 플랫폼에 따라서 크기가 정해진다

  • 32-bit 에서는 Int32 사이즈 제공
  • 64-bit 에서는 Int64 사이즈 제공

특별한 사이즈의 정수가 필요한게 아니라면, 항상 Int를 사용해라. 이것은 코드의 일관성과 상호운용성에 도움을 준다. 32비트 플랫폼에서 Int-2,147,483,648 에서 2,147,483,647 사이의 값을 저장할 수 있으며, 이는 정수의 범위에서 충분하다

상호운용성 : 동일한 시스템의 일부로 상호 작용하고 동일한 종류의 데이터 구조에서 작동할수 있음을 의미, 하나의 시스템이 이 기종의 다른 시스템과 제약 없이 호환되어 사용할 수 있는 성질

UInt

스위프트는 부호 없는 정수타입도 지원을한다. 32bit, 64bit에 맞게 크기가 지정된다.

필요한 경우가 아니라면 Int를 사용해라. 일관적인 Int의 사용은 코드의 상호운용성에 도움을 주고, 서로 다른 숫자 타입 사이에서 전환이 필요가 없다

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

부동 소수점 숫자는 분수요소를 포함하는 숫자이다. 3.14159, -273.15 등

부동 소수점 타입은 정수 타입보다 넓은 범위를 표현 가능하고 더 크고 작은 숫자들을 저장하는 것이 가능하다. 스위프트에서는 두 개의 부호를 가진부동 소수점 타입을 제공한다.

  • Double 64-bit의 부동 소수점 넘버
  • Float 32-bit의 부동 소수점 넘버

Double의 정밀도는 15자리의 소수점이하 자릿수인 반면에 Float은 6자리 소수점이하 자릿수이다. 적절한 부동소수점 타입의 사용은 코드에 특성과 값의 범위에 따라 다르지만, 주로 Double을 선호한다.

Type Safety and Type Inference (타입 안정성과 타입 유추)

스위프트는 타입세이프 언어이다. 타입세이프 언어는 코드가 좀 더 명확한 값의 유형을 갖게 하라고 권장한다

스위프트는 타입안전 때문에, 타입 체크를 컴파일 할 때 체크하고, 일치하지 않는 타입을 오류로 표시한다. 이것을 통해 개발과정에서 에러를 가능한 한 빨리 찾고 수정할 수 있게 도와준다

타입을 명시하지 않는 경우에 스위프트는 타입추론을 통해 대략적인 타입을 유추한다. 타입 추론은 컴파일러가 코드를 컴파일 할 때 주어진 값을 검사하여 특정 표현식의 유형을 자동으로 추론을 가능하게 한다

타입 추론 때문에 스위프트는 C언어와 Objetive-C 보다는 적은 타입 선언을 요구한다. 상수와 변수는 여전히 명시적으로 적어야하지만, 유형을 지정하는 작업의 대부분은 나의 몫이다.(or 대부분은 끝났다..? 자동으로 했다는 의미가 더 맞을지도)

타입 추론은 상수와 변수의 초기값을 선언할 때 특히 유용하다. 이것은 종종 상수나 변수를 선언한 시점에 리터럴 값을 할당함으로써 수행한다. (리터럴 값은 값이고, 소스 코드에 바로 나타난다. 아래 예에서 42, 3.14159와 같음)

literal : 소스 코드의 고정된 값을 대표하는 용어, 변수 초기화에 종종 사용된다. 데이터라고 생각하면 될 듯

예를 들어, 42라는 리터럴 값을 새 상수에 타입 명시 없이 선언한다면, 스위프트는 Int 라고 추론한다 왜냐하면, 정수처럼 보이는 값으로 초기화를 했기 때문이다.

let meaningOfLife = 42
// meaningOfLife는 Int로 추론된다.

let pi = 3.14159
// pi는 Double로 추론된다.
// 스위프트는 항상 Float 대신 Double로 추론한다

let anotherPi = 3 + 0.14159
// 3 이 명시적인 타입이 없고, 추가로 부동 소수점 리터럴이 있기 때문에
// anotherPi는 Double 로 추론된다.

Numeric Literals (숫자 리터럴)

정수 리터럴은 다음과 같이 적을 수 있다.

  • 접두사 없이 쓰이는 10진수
  • 0b 접두사와 같이 쓰이는 2진수
  • 0o 접두사와 같이 쓰이는 8진수
  • 0x 접두사와 같이 쓰이는 16진수
// 17을 표현한 정수 리터럴들
let decimalInteger = 17
let binaryInteger = 0b10001 
let octalInteger = 0o21  
let hexadecimalInteger = 0x11 

부동 소수점 리터럴은 10진수,16진수(0x로 시작되는)으로 표현이 가능하다. 소수점 양쪽에는 항상 숫자나 16진수가 있어야 한다. 10진수 소수는 e 로 표시되는 지수를 가질 수 있다. 16진수 소수는 반드시 p를 가진다.

10진수 소수의 지수가 exp인 경우에 기본숫자에 10^exp 를 곱한 의미를 가진다.

  • 1.25e2 는 1.25*10^2, or 125.0 이다
  • 1.25e-2 는 1.25*10^-2 or 0.0125 이다

16진수 소수의 지수가 exp인 경우에 기본숫자에 2^exp 를 곱한 의미를 가진다.

  • 0xFp2 는 15*2^2 or 60.0 이다.
  • 0xFp-2 는 15*2^-2 or 3.75 이다.
//12.1875의 부동 소수점 리터럴의 값의 표현들
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

숫자 리터럴은 더 읽기 쉽게 해주는 추가 형식을 포함하고 이 형식은 리터럴의 기본 값에 영향을 미치지 않는다.

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

Numeric Type Conversion (숫자 타입 변환)

음수를 쓰지 않더라도 대체적으로 Int 타입을 사용해라. 매번 Int를 사용하는 것은 정수 상수와 변수가 코드에서 즉시 상호운용 할 수 있고, 정수 리터럴 값에 대해 추론된 타입과 일치할 것을 의미한다

외부 소스에서 명시적으로 지정된 데이터, 성능, 메모리 사용량, 다른 필수 최적화를 위해 특별히 필요한 경우에만 다른 정수 타입을 사용해라. 이러한 경우에 명시적으로 지정된 타입을 쓰는 것은 값의 오버플로나, 사용중인 데이터의 특성을 암시적으로 문서화 하는데 도움을 준다.

Integer Conversion (정수 변환)

정수 상수나 변수가 저장될 수 있는 숫자의 범위는 숫자 타입마다 다르다. (Int8의 경우 -128~127, UInt8의 경우 0~255) 숫자에 알맞지 않은 범위의 값이 들어간다면 코드를 컴파일 할 때 오류를 보고한다

// 범위에 맞지 않으므로 에러를 보고함
let cannotBeNegative: UInt8 = -1
let tooBig: Int8 = Int8.max + 1

각각의 숫자 타입은 다른 범위의 값을 저장 할 수 있기 때문에, 숫자 타입의 변환은 사례에 맞게 택해야 한다. 이 opt-in 방식은 숨겨진 변환 에러를 막아주고 코드에 타입 변환 의도를 명시적으로 만드는 것을 도와준다

하나의 특정 숫자 타입을 다른 타입으로 변환하려면 기존 값에 원하는 숫자 타입의 값으로 초기화하면 된다.

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1

// 덧셈을 가능하게 해준다
let twoThousandAndOne = twoThousand + UInt16(one)

twoThousandAndoneUInt16으로 유추가 된다 왜냐하면 UInt16끼리 덧셈을 하였기 때문이다.

SomeType(ofInitialValue) 은 스위프트의 이니셜라이저를 호출하고 초기 값을 전달하는 기본 방법이다. 이전에 UInt16UInt8의 값을 허용하는 이니셜라이저를 가지고있고, 기존의 UInt8을 새로운 UInt16로 만드는데 사용된다. 여기서 any 타입은 전달할 수 없다. UInt16 가 제공하는 이니셜라이저만 전달이 가능하다.

번역하려고 하니 말이 좀 이상한 것 같은데 요점은 변환이 가능한 타입이 있고 불가능한 타입이 있다는 것이다.

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

정수와 부동 소수점 숫자의 변환은 명시적으로 이루어져야 한다

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi는 3.14159로 Double로 유추된다.

이 계산은 상수인 three의 값이 새로운 타입 유형인 Double로 만들어져서 허용된다.

부동 소수점을 숫자로 변환하는것도 명시적으로 이루어지고, 항상 정수의 소수점은 잘라서 표현한다. (4.75를 정수로 변환하면 4)

숫자 상수나 변수를 결합하는 규칙은 숫자 리터럴 규칙과는 다르다. 리터럴 3은 0.14159랑 덧셈이 가능하다. 왜냐하면 숫자 리터럴은 그들 스스로 명시적인 타입이 없기 때문이다. 해당 타입은 컴파일러에 의해 평가되는 시점에서 유추된다.

Type Aliases (타입 별칭)

Type Aliases 는 기존의 타입을 다른 이름으로 정의 한다.

이것은 기존 타입을 문맥상으로 좀 더 적절하게 참조하려고 할 때 유용하다. 예를 들면, 외부 소스로 부터의 특정 사이즈의 데이터로 같이 작업을 하는 경우 등

typealias AudioSample = UInt16

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound는 0의 초기값을 가진다
// AudioSample.min == UInt16.min 과 같다

Booleans (부울)

스위프트는 간단한 불린 타입을 가지고 있고 Bool 이라 부른다. 불린 값은 참, 거짓 값만 될 수 있기 때문에 논리값이라고 불린다.

let orangesAreOrange = true
let turnipsAreDelicious = false
// 불린 리터럴 값으로 초기화 되었다는 사실로 Bool로 유추가 된다.

위의 Int, Double와 마찬가지로 생성시에 true, false 값으로 초기화 한다면 Bool이라고 타입을 선언할 필요가 없다. 값의 타입을 이미 알고있는 다른 값과 함께 상수나 변수로 초기화 할 때 타입 유추 기능은 스위프트 코드를 더 간결하고 읽기 쉽게 해준다

불린 값은 특히 조건문을 사용할 때 유용하다.

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

스위프트의 타입 안전기능은 불린이 아닌 값이 불린으로 대체 되는 것을 막아준다.

let i = 1
if i {
    // 이 예제는 컴파일 되지 않고, 에러를 띄울것이다.
}

if i == 1 {
  // 이 예제는 성공적으로 컴파일 된다.
}

위의 i == 1의 비교 결과의 경우에는 불린 타입이므로, 타입 체크를 통과한다

스위프트의 타입 안전성에 다른 예와 마찬가지로, 이 접근은 갑작스러운 에러를 피하고, 코드의 특정 구역에 의도를 항상 명확하게 보장한다

Tuples (튜플)

튜플은 다중 값들을 단일 복합값으로 그룹화한다. 이 튜플내의 값은 어떤 타입도 올 수 있고, 서로 같은 타입이 아니여도 된다.

밑의 예는 HTTP의 상태 코드를 묘사한 튜플이다. HTTP 상태 코드는 웹 페이지를 요청할 때마다 웹서버에서 반환하는 특별한 값이다. 404 상태 코드는 웹페이지가 존재하지 않을 때 반환된다.

let http404Error = (404, "Not Found")
// http404Error의 타입은 Int, String으로 묶여있다
// 하나는 숫자, 하나는 사람이 읽을 수 있는 설명을 개별 값으로 제공한다.
// 이것은 (Int, String) 튜플로 설명할 수 있다.

타입의 모든 순열에서 튜플을 만들 수 있고, 원하는 만큼 다양한 유형을 포함 할 수 있다.

튜플의 요소를 별도의 상수나 변수로 분해하여 평소처럼 접근이 가능하다.

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"

튜플은 함수의 반환값으로서 특히 유용하다. 웹 페이지 검색을 시도하는 함수는 페이지 검색의 성공이나 실패를 설명하기 위해 (Int, String) 튜플을 반환 할 수 있다. 결과에 관한 하나의 값을 리턴하는것 보다 별개의 다른 타입의 값을 리턴함으로써, 더 유용한 정보를 제공한다.

NOTE

튜플은 간단히 관련된 값들의 그룹에서 유용하다. 복잡한 데이터 구조 생성에서는 적합하지 않다. 만약 데이터 구조를 더 복잡하게 만들고 싶다면, 클래스나 구조체를 사용해라

Optionals (옵셔널)

값이 없는 어디서든 optional 을 사용할 수 있다. 옵셔널은 두개의 가능성을 가지고 있다 값을 언래핑해서 값에 접근할 수 있다는 것과, 값이 없다는 가능성이다.

NOTE

옵셔널의 개념은 C와 Objective-C 에서는 없는 개념이다. 가까운 개념으로는 Objective-C에 객체를 반환하는 메소드에서 nil을 리턴하는 개념이다. 이것의 의미는 유효한 객체의 부재를 의미한다. 그러나 이는 객체에서만 기능하고, 구조나 C의 기본타입, 열거형에서는 기능하지 않는다. 이런 타입들을 위해, Objective-C 메소드는 보통 값의 부재를 의미하는 특별한 값(NSNotFound)을 리턴함. 이 접근은 메소드의 호출자가 테스트 하기 위한 특별한 값이 있다는 것을 알고 이를 확인 하는 것을 기억한다고 가정한다. 스위프트의 옵셔널은 어떤 타입이던지 특별한 상수의 필요 없이 값의 부재를 알려준다.

스위프트의 Int 유형은 String을 Int로 변환해주는 이니셜라이저를 가지고 있다. 그러나 항상 변환되는 것은 아니다.

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 값을 포함하고 있거나, 값이 없음을 의미하는 것을 나타낸다.
// Bool, String 값은 포함할 수 없으며, Int나 값이 없다는 의미는 가능하다.

nil

옵셔널의 값에 nil 이라는 값이 없는 상태를 할당할 수 있다.

var serverResponseCode: Int? = 404
// 404의 실제 Int 값이 포함된다.
serverResponseCode = nil
// 현재 값이 없는 상태를 의미한다.

NOTE

옵셔널이 아닌 상수나 변수에 nil을 사용할 수는 없다. 코드의 상수나 변수가 특정 조건 하에 값의 부재 상태를 작동해야 하는 경우 항상 해당 타입의 옵셔널 값을 선언해라

기본 값없이 옵셔널 값을 선언하는 경우, 그 값은 자동적으로 nil을 지정함

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

NOTE

스위프트의 nil은 Objective-C에 nil과 같지 않다. Objective-C의 nil은 존재하지 않는 객체에 대한 포인터이다. 스위프트에서는 포인터가 아니며, 값의 부재를 나타내는 특정 타입이다. 객체 타입 뿐만 아니라, 어떤 타입의 옵셔널도 nil을 지정할 수 있다.

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

If 문을 사용하여 옵셔널을 nil과 비교를 하여 옵셔널에 값이 포함되어 있는지 확인이 가능하다. ==, != 기호를 사용할 수 있다.

// 옵셔널에 값이 있다면 nil과 같지 않다를 의미 함
if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// Prints "convertedNumber contains some integer value."

옵셔널에 값이 포함되어 있다고 확신을 하면 이름 끝에 느낌표를 추가하여 접근이 가능하다. 느낌표는 "나는 옵셔널이 값을 가지고 있음을 알고있고, 사용해라" 라는 의미다. 이것은 Forced Unwrapping(강제 언래핑) 이라고 한다.

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

NOTE

존재하지 않는 값에 느낌표 사용은 런타임 에러를 유발한다. 항상 느낌표를 사용하기전에 nil 값이 없음을 확신해라.

Optional Binding(옵셔널 바인딩)

옵셔널의 값의 유무를 확인 할 때 Optional Binding을 사용 가능하고, 그렇다면 임시 상수나 변수로서 해당 값을 사용하게 해준다. 옵셔널 바인딩은 if, while문과 같이 사용하여 옵셔널에 값의 유무를 체크하고, 단일 행동 중 하나로 상수나 변수로 부터 값을 추출한다.

if 문에서의 옵셔널 바인딩은 다음과 같다

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)\" couldn't be converted to an integer")
}
// Prints "The string "123" has an integer value of 123"

위 코드의 의미는 "Int(possibleNumber)에 의해 반환된 optional Int에 값이 포함되어 있다면, 새로운 상수 actualNumber에 옵셔널에 포함된 값을 지정하라" 라는 의미이다.

변환이 성공적이라면, if문의 첫 분기에서 사용 가능하게 된다. 이미 옵셔널에 포함된 값으로 초기화 되었으므로, 느낌표를 사용하지 않아도 된다.

상수나 변수 둘다 옵셔널 바인딩을 사용 가능하다. 만약 if문 첫 분기에 값을 변경하고 싶다면 if var로 선언하면 변경이 가능하다.

많은 옵셔널 바인딩과 Boolean 조건문을 하나의 if문에 필요한 만큼 사용이 가능하다. (콤마로 구분하여) 옵셔널 바인딩의 어떤 값이 nil 이나 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"

NOTE

if문 내부에 옵셔널 바인딩으로 만든 상수나 변수는 if문 바디에서만 사용이 가능하다. 반대로 guard문으로 만든 상수나 변수는 guard문 다음 줄부터 사용이 가능하다.

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

옵셔널은 값이 존재한다면 접근하기위해 옵셔널 바인딩으로 조건부로 래핑을 해제할 수 있다.

때로는 프로그램 구조에서 옵셔널이 값을 지정한 후에, 옵셔널이 항상 값을 가지는것은 분명하다. 이런 경우에 항상 값을 가지고 있다고 안전하게 가정이 되기 때문에, 값을 확인하고, 접근할 때마다 언래핑 하는 필요를 없애는 것은 유용하다.

이런 옵셔널의 종류는 Implicitly Unwrapped Optionals라 정의된다. 옵셔널을 선언할 때 물음표 대신 느낌표로 선언을 하는것이 좋다.(사용x 선언o)

암시적으로 언래핑된 옵셔널은 옵셔널이 처음 정의된 직후 값이 존재하는 것으로 확인 되고 그 이후 모든 지점에 존재한다고 확실히 가정할수 있을 때 유용하다.

암시적 언래핑 옵셔널은 내부적으로 옵셔널이지만, 옵셔널 값에 접근할 때마다 언래핑할 필요 없이 옵셔널이 아닌 값처럼 사용이 가능하다.

// 옵셔널과 암시적 언래핑 옵셔널의 비교 예제
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 느낌표가 필요하다.

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 필요 x

암시적 언래핑 옵셔널은 필요한 경우 옵셔널을 강제로 풀 수 있는 권한을 부여하는 것으로 생각할 수 있다. 암시적 언래핑된 옵셔널을 사용할때, 스위프트는 처음 보통의 옵셔널로서 사용을 시도한다. 옵셔널로 사용이 불가하면, 스위프트는 강제로 값을 언래핑한다. 위 코드에서 implicitString는 명시적으로 옵셔널이 아니기 때문에 할당 전에 assumedString를 언래핑한다.

밑의 코드는 명시적인 타입이 아니므로 보통의 옵셔널로 사용하려고 한다

let optionalString = assumedString
// 옵셔널이기 때문에 강제로 언래핑 되지 않는다

암시적 언래핑 옵셔널이 nil이고 그것의 래핑된 값에 접근할 시에 런타임 에러가 발생한다. 이 결과는 보통의 옵셔널에 값이 없이 느낌표를 붙이는 것과 정확히 같다.

암시적 언래핑 옵셔널이 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 (에러 처리)

프로그램의 실행중에 에러를 마주친다면 error handling을 사용할 수 있다.

값의 유무를 사용하여 함수의 성공과 실패에 전달하는 옵셔널과 달리 에러처리는 실패의 원인을 판별하고, 필요하다면 다른 프로그램의 부분으로 전파 할 수 있다.

함수가 에러를 마주했을 때 에러를 던진다고(throws) 한다. 함수의 호출자는 에러를 catch 할 수 있고 에러에 대한 반응을 적절하게 할 수 있다.

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

함수는 에러를 나타낼 때 throws 키워드와 같이 선언하여 나타낸다. 에러를 발생시키는 함수를 호출할 때, 표현식 앞에 try 키워드를 추가 해야 한다.

스위프트는 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()가 호출된다. 에러가 호출되고 두번째 케이스라면 washDishes() 가 호출된다. 세번째 케이스와 같다면, catch 패턴에서 캡쳐한 [String] 값과 함께 buyGroceries(_:)가 호출된다.

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

주장과 전제조건은 런타임때 일어나는 검사이다. 다른 추가 코드의 실행전에 필수 조건을 만족시키는것을 확실히 할 때 사용한다. 만약 주장이나 전제조건에 불린 컨디션의 평가가 true라면 코드의 실행은 평소와 같이 실행되고, false시에는 프로그램에 현재 상태는 유효하지 않다. 코드 실행이 종료되고 앱이 종료된다.

주장과 전제조건을 사용하여 코딩하는 동안 가정과 기대치를 표현하므로 이를 코드의 일부로 포함 할 수 있다. 주장은 개발하는 동안에 맞지 않는 가정과 실수를 찾는데 도와주고 전제조건은 제품의 결함을 찾는것을 도와준다.

런타임때 유효한 기대를 더하여, 주장과 전제조건은 코드의 문서화 형태에 도움을 준다. 위의 오류 처리에서 설명한 오류 조건과 달리 주장과 전제조건은 복구 가능 하거나 예상되는 오류에는 사용 되지 않는다. 주장과 전제조건의 실패는 유효하지 않은 프로그램 상태를 나타내므로 에러를 찾을 방법이 없다.

주장과 전제조건을 사용하는 것은 잘못된 조건이 발생할 가능성이 낮은 방식으로 코드를 설계하는 것을 대신할 수는 없다. 그러나 유효한 데이터와 상태를 적용하기 위해 사용하는 것은 유효하지 않은 상태의 발생으로 종료되는 경우의 예상이나 문제의 해결을 더 쉽게 도와주게 한다. 유효하지 않은 상태가 감지되는 즉시 실행을 멈추는것은 유효하지 않은 상태로 인한 데미지를 최소화 해주는데 도움이 된다.

주장과 전제조건 사이의 차이점은 언제 체크 되는지에 따라 다르다. Assertion(주장)은 디버그 시에만 체크하고 precondition(전제조건)은 디버그와 빌드 둘다 체크한다. 빌드시에 주장안에서의 조건들은 평가되지 않는다. 이것은 개발 과정에서 많은 주장을 사용하더라도 제품의 성능에는 크게 영향을 끼치지 않음을 의미한다.

Debugging with Assertions (주장에 의한 디버깅)

assert(_:_:file:line:) 로 주장을 사용 가능하다. 만약 false 시에 line 부분에 메시지를 출력하는 형태이다. true의 경우에는 통과.

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가 조건에 맞지 않으므로 종료된다. 메시지를 생략하는것도 가능하다. asssert(age >= 0)

만약 코드가 이미 조건을 체크하는 경우 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 Preconditions (강제 전제조건)

잠재적인 실패의 가능성이 있을때마다 전제조건을 사용해라. 그러나 계속 코드를 실행하려면 반드시 true여야 한다. 예를 들어, 범위를 벗어나지 않게 확인하거나, 함수에 유효한 값을 넘겨 주었는지를 확인하는 경우에 사용해라.

preconditon(_:_:file:line:)로 사용 가능하다. assert와 마찬가지로 true시에는 통과, false일 때는 메시지를 출력해준다.

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

또한 preconditonFailure(_:file:line:)도 사용 가능하며, 실패가 발생했음에 대해 나타내준다. 예를들어, switch문에서 default 케이스로 사용했지만, 모든 유효한 입력 데이터가 다른 케이스에 적용이 되어야 함을 알려줌.

NOTE

컴파일 시에 -Ounchecked로 설정하면 전제조건은 체크 되지 않는다. 컴파일러는 전제조건들이 항상 참이라고 가정하고 코드에 최적화 한다. 그러나 fatalError(_:file:line:)는 최적화 설정에 관계없이 항상 실행을 멈춘다.

프로토타이핑 및 초기 개발 중에 구현되지 않은 기능들을 fatalError(_:file:line:) 사용하여 임시로 만들 수 있고 fatalError("Unimplemented")로 작성한다. 주장과 전제조건과 달리 치명적인 실수들은 절대 최적화 되지 않기 때문에, 임시로 구현한 기능이 발생하면 항상 실행을 멈추는것을 확실하게 해야한다.


오역을 최대한 줄이려고 시도를 하였긴 하나 어느정도는 있을 것 같다고 생각한다..

profile
hi there 👋

0개의 댓글