The Swift Programming Language Documents를 참고해서 정리했습니다.
여러 가지 생각들이 버무러져 있습니다.
contents
️ Swift
safe, fast, interactive programming language
안전하고, 빠르고, 상호작용하는 프로그래밍 언어
Swift는 modern programming pattern를 채택하기에 일반적인 프로그래밍 오류를 큰 범위로 정의했습니다.
•
변수는 항상 사용전에 초기화되어야 합니다.
•
Array indices에서 out-of-bound 에러가 발생하는지 확인합니다.
•
Integer의 overflow 여부를 확인합니다.
•
Optional를 사용하면 nil을 명시적으로 다룹니다.
•
메모리를 자동으로 관리합니다.
•
에러 핸들링을 통해서 예상치 못한 문제로부터 복구를 제어합니다.
또한, 강력한 타입 추론, 패턴 매칭을 사용하여 쉽게 코드를 읽고 유지 보수할 수 있습니다.
️ Summary
The Basic에서는 일반적인 데이터 작업 및 기본 구문 작성을 위한 내용을 디테일하게 설명해주는 식으로 전개합니다. 따라서, 해당 내용들을 이미 충분히 잘 알고 있다면 다른 챕터로 넘어가는걸 추천드립니다.
The Basic이 가지고 있는 내용을 가볍게 훑고 디테일한 내용으로 넘어갑시다.
Swift는 C, Objective-C에서 다루는 기본적인 타입들을 제공합니다.
•
Int, Double, Float, Bool, String
그 외에도 3가지 주요 Collection Type를 제공합니다.
•
Array, Set, Dictionary
Swift는 값을 저장하고 참조하는 용으로 변수를 사용하고 값이 변경되지 않는 변수, 즉 상수를 사용합니다.
변경할 필요가 없는 값이라면 Swift에서는 상수로 작성하면 됩니다. Swift에서의 상수는 C에 있는 상수보다 파워풀합니다. 코드를 안전하게 만들어주고 작성자의 의도를 더 명확하게 해주는 장점이 있습니다.
또한, Objective-C에서도 찾을 수 없는 타입인 Tuple를 도입했습니다.
덕분에 multiple한 값을 single compound 값으로 반환할 수 있습니다.
Swift는 Optional Type도 도입했습니다. 덕분에 Swift에서는 값의 존재를 다룰 수 있습니다.
해당 변수의 값이 nil이라면 우리는 “There isn’t a value at all.” 이라는 걸 알 수 있습니다.
Objective-C에서 nil pointer를 사용하는 것과 비슷하지만 Optional은 nil pointer와 다르게 클래스뿐만 아니라 다른 타입도 사용할 수 있습니다. 따라서, 더 안전하게 Swift를 사용할 수 있게 됩니다.
Swift는 Type-safe language 입니다. 그렇기에 값의 타입을 명확하게 해주어야 합니다.
예를 들어서, String를 요구하는 상황에서 Type-safe는 실수로 Int가 들어가는 상황을 막습니다. 또한, non-optional String에 optional String이 들어가는 문제도 막을 수 있습니다.
즉, 개발 과정에서 잘못된 부분을 찾고 고칠 수 있게 도와줍니다.
오늘 The Basic 포스팅에서 얘기할 내용을 함축적으로 적어 봤습니다. 이젠 자세하게 한 번 알아볼게요.
️ The Basic
상수(Constants) & 변수(Variables)
상수와 변수는 특정 타입의 값의 이름과 관련이 있습니다.
사용되기 전에 꼭 선언을 해야하며 각자의 Keyword 로 선언해주면 됩니다.
// Constant
let maximumNumberOfLoginAttemps = 10
// Variable
var currentLoginAttempt = 0
Swift
복사
•
상수는 let 키워드로 선언되며 절대 변경되지 않는 값으로 선언해주면 됩니다.
예시로 써둔 maximumNumberOfLoginAttemps 는 절대 바뀌지 않을 값이기 때문에 상수로 선언할 수 있습니다. 해당 상수를 선언하고 나서 값을 변경하려고 한다면 컴파일 에러가 발생합니다.
•
변수는 var 키워드로 선언되며 변경되는 값으로 선언합니다.
미래에 언젠가 변경할 필요가 있다면 변수를 사용하는 것이 좋습니다. 예시로 써둔 currentLoginAttempt 는 추가되는 시도 횟수에 따라서 변경될 수 있기 때문에 변수입니다.
상수, 변수를 선언할 시에 각 상수와 변수에 어떤 Type의 값이 저장될 수 있는지 적어줄 수 있는 Type Annotation이 제공됩니다.
var message: String
Swift
복사
라는 코드를 봤을 때, “String 타입의 message라고 불리는 변수 선언” 이라고 해석하면 됩니다.
message는 String 타입을 저장할 수 있다는 걸 명확하게 알 수 있습니다.
하지만, 실제로 Type Annotation를 작성해야 하는 경우는 거의 없습니다.
상수, 변수가 정의되는 시점에 초기값을 제공하면 Swift에서는 초기값을 통해서 해당 상수, 변수에 대한 타입을 추론합니다. 만약, 초기값이 제공되지 않는다면 Type Annotation으로 지정해주어야 합니다.
상수와 변수의 이름은 어떤 character든 포함합니다.
하지만, whitespace, mathematical symbol, arrow, private-use Unicode scalar value, line- and box-drawing character 는 불가능합니다.
숫자를 이름 안에 넣을 수는 있지만, 이름 맨 앞에 작성하는건 안됩니다.
Swift에 있는 Keyword와 동일한 이름으로 이름을 지어야 한다면 backtick(``)을 사용해서 이름을 둘러싸야 합니다. Keyword와 동일한 이름은 선택의 여지가 없는 경우에만 사용하는게 좋습니다.
한 번 특정 타입으로 상수, 변수를 선언하고 나면 같은 이름으로 다른 상수, 변수를 선언할 수 없습니다. 또한, 한 번 선언한 상수, 변수의 타입을 변경하는 것도 불가능합니다. 하지만, 이미 존재하는 변수의 값을 같은 타입을 가진 값으로 변경하는 건 가능합니다.
이러한 상수, 변수 값을 확인하기 위해서 print() 메서드를 사용하게 될 수 있습니다.
\() 를 String 값 내부에 넣고 그 안에 상수, 변수의 이름을 적으면 이름이 콘솔창에는 값으로 대체되어서 나타납니다.
let message = "Hello world"
print("Print: \(message)")
// Print: Hello world
Swift
복사
Types
Integers
Integer는 소수점없는 숫자 관련된 타입입니다.
Signed(음수, 0, 양수), Unsigned(0, 양수)로 나눠서 Integer 타입을 만들 수 있고, 8, 16, 32, 64 비트 형식으로 나눠서 Integer 타입을 만들 수 있습니다.
예를 들어서, 부호가 없는(Unsigned) 8비트 형식의 정수형을 만든다고 한다면 UInt8 타입을 사용하면 됩니다. 부호가 있는(Signed) 32비트 형식의 정수형을 만든다고 한다면 Int32 타입을 사용하면 됩니다.
정수는 범위를 가지는데, Signed 32비트 정수의 경우에는 -2,147,483,648 ~ 2,147,483,647 의 범위를 가집니다.
그렇다면 우리가 매번 사용할 정수값의 범위를 생각해서 타입을 선택해주어야 할까요?
아닙니다. 대부분의 경우 Integer의 특정 사이즈를 선택할 필요가 없습니다. Swift에서 Int 를 제공해주니깐요.
Int 타입은 현재 플랫폼의 native word의 사이즈를 가집니다. 32bit 플랫폼이면 Int32, 64bit 플랫폼이면 Int64 타입을 가집니다. 그렇다고, Int64 와 Int 가 같은 타입이 되는건 아닙니다.
특정 크기의 정수로 작업할 필요가 없다면, 항상 Int 타입으로 사용하는 것이 좋습니다. 코드의 일관성 및 타입의 제약없이 호환하기 위해서는 Int 타입으로 사용하는게 특정 비트나 Signed, Unsigned를 붙이는 거보다 좋습니다.
음수를 사용하지 않는 상황에서는 UInt를 사용하는게 좋지 않을까요?
그렇더라도 Int 타입을 사용하는 것이 위와 같은 이유로 좋습니다.
그렇다면, 특정 크기의 정수 타입은 언제 사용하는건가요?
외부 소스의 데이터 크기를 명시하거나, 성능, 메모리 사용, 기타 최적화를 위해서 사용합니다. 명시적인 크기 타입을 사용하면 우발적인 overflow를 파악할 수 있고 사용 중인 데이터 특성을 문서화하는데 도움을 줍니다.
정수는 변수, 상수의 형식으로 쓸 수도 있지만, Numeric Literal의 형태로도 나타낼 수 있습니다.
// decimal(no prefix)
let decimalValue = 17
// binary(0b prefix)
let binaryValue = 0b10001
// octal(0o prefix)
let octalValue = 0o21
// hexadecimal(0x prefix)
let hexadecimalValue = 0x11
Swift
복사
Floating-Point Numbers
소수점을 가진 숫자와 관련된 타입입니다.
Floating-point Number는 Integer보다 더 넓은 범위의 값을 가집니다. 따라서, Integer보다 더 크고, 작은 값들을 저장할 수 있습니다.
두 가지의 Signed Floating-point Number 타입이 있습니다.
•
Double: 64bit floating point number, 최소 15자리 소수점
•
Float: 32 bit floating point number, 최소 6자리 소수점
타입은 작업해야 하는 값의 특성과 범위에 따라서 선택하면 됩니다. 만약, 두 가지 모두 조건을 충족한다면 Double 타입을 고르시는게 좋습니다.
소수점을 가진 숫자들은 변수, 상수의 형식으로 쓸 수도 있지만, Numeric Literal의 형태로도 나타낼 수 있습니다.
// decimal(no prefix)
let decimalValue = 12.1875
// decimal with exponent(e)
let exponentDecimalValue = 1.21875e1
// hexadecimal(0x prefix)
let let hexadecimalValue = 0xC.3p0
Swift
복사
이러한 literal 값들은 쉽게 읽기 위해서 추가 형식을 포함할 수 있는데, 이는 기본 값에 영향을 미치지 않습니다.
// 0를 추가로 채우기
let addingZero = 000123.345
// 밑줄 포함
let oneMillion = 1_000_000
Swift
복사
Conversion
Type이 다른 Type으로 변환하려고 한다면 Type Conversion를 사용해야 합니다.
변환 시 생기는 오류를 방지하기 위함도 있고, 타입 변환 의도를 코드에 명시하기 위함도 있습니다.
특정 Integer 타입을 다른 Integer 타입으로 변환한다고 할 때,
let unsignedNum: UInt16 = 2
let unsigned8Num: UInt8 = 1
let number = unsignedNum + UInt16(unsigned8Num)
// 3
Swift
복사
UInt16과 UInt8은 다른 타입이기 때문에 직접 덧셈 연산을 할 수 없습니다. 따라서, 하나의 타입을 다른 타입으로 변환시켜줌으로써 연산을 할 수 있게 됩니다.
물론, 아무 타입이나 UInt16으로 변환할 수 있는건 아닙니다. UInt16에서 제공하는 이니셜라이저에서 허용하는 타입만이 해당 타입으로 변환할 수 있습니다. UInt8이 해당 타입에 포함되기 때문에 변환이 가능했던 겁니다.
그렇다면, Int 타입과 Floating-Point Number 타입을 더한다고 할 때는 어떤 일이 벌어질까요?
위처럼 명시적으로 타입 변환을 하면 됩니다.
let three: Int = 3
let piFloating: Double = 0.14159
let number = Double(three) + piFloating
// 3.14159
Swift
복사
Double, Float 타입을 Int 타입으로 변환하게 될 시에는 소수점 부분이 잘리기 때문에 이 부분을 유념해서 사용해야 합니다.
Numeric 상수, 변수를 사용해서 덧셈을 하는 예시를 작성해보았습니다. 그렇다면 Numeric literal를 사용해서 덧셈을 한다면 동일하게 타입 변환을 해주어야 하는걸까요?
아닙니다. 위와 동일한 식이지만 Numeric literal를 사용해서 코드를 작성해보겠습니다.
let piFloating: Double = 0.14159
let number = 3 + piFloating
// 3.14159
Swift
복사
위의 three 상수와 동일한 3 value를 가지고 있지만 Double로 변환을 해주지 않아도 됩니다.
왜일까요?
numeric literal은 명시적인 타입이 없기 때문입니다. 따라서, 컴파일러가 3를 Int 타입이 아닌 Double로 추론을 한겁니다. 따라서, 이전처럼 타입 변환을 해주지 않아도 되는거죠.
뒤에 있는 piFloating 상수의 타입이 Double이기 때문에 이를 보고 컴파일러가 Double로 추론을 해준겁니다.
Type Safety & Type Inference
Swift는 Type-safety language 입니다.
따라서, String 타입이 필요한 부분에 실수로 Int 타입을 전달할 수 없게끔 타입을 지정합니다.
컴파일 시에 타입이 일치하는지 확인하고 일치하지 않으면 오류를 표시하여 가능한 빨리 오류를 수정할 수 있도록 도와줍니다.
그렇다고, 모든 상수, 변수에 타입을 지정해야 하는 건 아닙니다.
필요한 값의 타입을 지정하지 않으면 Type Inference를 사용해서 적절한 타입을 계산하게 됩니다. 컴파일 시에 컴파일러가 해당 값의 검사해서 적절한 타입을 자동으로 추론해주는겁니다.
덕분에 다른 언어들보다 적게 선언해줄 수 있습니다.
let num = 3
let floatingPointNum = 3.14159
Swift
복사
위의 코드가 있으면 Swift는 num의 타입을 Int 타입으로, floatingPointNum의 타입을 Double 타입으로 추론합니다.
Floating-point number는 따로 지정한 타입이 없다면 Double 타입으로 추정합니다.
Boolean
Boolean 타입은 항상 논리적인 true / false 값을 가집니다.
초기화 시에 Bool 상수로 값을 초기화하면 Boolean 타입으로 추론합니다.
let hasEnterKey = true
Swift
복사
하지만, Swift에서는 non-Boolean 타입이 Boolean를 대체하는걸 방지합니다. 1과 0으로 true와 false를 나타낼 수 없다는 뜻입니다. non-Boolean 타입을 Boolean 대체로 사용하면서 생기는 오류를 방지하고 의도를 항상 명확하게 하도록 보장하기 위해서 대체를 방지합니다.
하지만, 이렇게 사용하는건 가능합니다. i와 1를 비교함으로써 발생하는 결과가 Boolean 타입이기에 그렇습니다.
let i = 1
if i == 1 { // code }
Swift
복사
Tuples
여러 값을 하나의 Compound 값으로 묶은겁니다.
Tuple에 들어가는 값의 타입은 어떤 타입이든 괜찮고, 서로 같은 타입이 아니어도 좋으며, 원하는만큼 조합을 만들어낼 수 있습니다.
함수의 return Type으로 유용하게 사용할 수 있으며 한 가지 값을 반환하는 것보다 결과에 대한 유용한 정보를 제공받기 좋습니다.
func request(_ request: URLRequest) -> (Int, URLResponse)
Swift
복사
반환되는 결과값은 한 가지 변수로만 받을 수도 있지만, 여러 형식으로 받아서 사용할 수 있습니다.
// 튜플 형식으로 받기
let (data, response) = request(urlRequest)
// 튜플 값 중 하나만 받기
let (_, response) = request(urlRequest)
// 한 가지 변수로 받아서 index로 사용
let response = request(urlRequest)
print("statusCode: \(response.0), urlResponse: \(response.1)")
// 한 가지 변수로 받아서 정의해둔 이름으로 사용
// func request(_ request: URLRequest) -> (statusCode: Int, urlResponse: URLResponse)
let response = request(urlRequest)
print("statusCode: \(response.statusCode), urlResponse: \(response.urlResponse)")
Swift
복사
Tuple에는 여러 값을 넣어서 보낼 수 있지만, 간단한 정보에 유용합니다.
복잡한 데이터 구조일 경우에는 Tuple보다는 클래스, 구조체를 사용해서 모델링하는 것이 좋습니다.
Optionals
값이 있을 수도 없을 수도 있는 상황에서 사용하는 타입입니다.
Optional은 값이 있고, 없고 하는 두 가지 가능성을 나타냅니다.
왜 값이 없음을 나타낼 수 있는 타입이 필요할까요?
예를 들어서, Int 타입의 이니셜라이저에 String 타입을 넣는다고 해봅시다. “123”같은 String 타입의 값은 Int 타입으로 충분히 변환이 될테지만 “Hello”라면 Int 타입으로 변환이 가능할까요?
let stringValue = "Hello"
let num = Int(stringValue)
Swift
복사
아마 변환이 실패할겁니다. 이럴 때 반환해줄 수 있는 값이 값이 없음을 나타내는 nil인겁니다.
따라서, num은 Int 타입이 아니라 Int? 타입의 상수가 됩니다.
? 마크가 없는 Int 타입은 non-optional 타입입니다. 즉, 어떤 상황에서도 값이 존재하는 타입입니다.
그렇기 때문에 Int? 타입과 Int 타입은 컴파일러가 다른 타입으로 인식합니다.
optional 타입은 non-optional 타입과 다르게 default value없이 선언할 수 있습니다. 이 때, 변수 안에 들어가는 값은 자동으로 nil로 설정됩니다.
var num: Int? // nil
Swift
복사
optional 타입의 값을 사용할 때에 값을 가졌는지, 가지지 않았는지 확인하는 방법이 있습니다.
그 중에 하나가 바로 if 문을 사용하는 겁니다.
if num != nil { // code }
Swift
복사
num이 nil이 아니라는건 num이 값을 가졌다는 뜻이기 때문에 num 안에 있는 값을 사용할 수 있습니다.
if 코드 블럭 내에서는 num에 value가 있는 상태입니다. 따라서, value를 가졌다고 보장할 수 있습니다. optional 타입이 nil이 아니라는걸 보장할 수 있다면 ! 를 끝에다가 붙여서 강제 언래핑(forced unwrapping)을 진행합니다.
강제 언래핑을 했을 때, optional value가 nil이라면 런타임 에러가 발생합니다. 따라서, 항상 non-nil value를 가지는지 생각하고 사용해야 합니다.
그렇다면, forced unwrapping를 통해서만 값을 가져올 수 있을까요?
아닙니다. optional binding를 통해서 value를 가졌는지 확인하고 해당 값을 일시적으로 사용할 수도 있습니다.
optional binding은 if let, guard let, while let 의 형태로 나타납니다.
// if let -> 값이 있으면 { } 안에서 사용
if let name = nameOptionValue { }
// guard let -> 값이 있으면 선언해준 { } 안에서 로컬 변수로 사용, 아니면 return으로 빠져나감
guard let name = nameOptionValue else { return }
// while let -> while문 안에서 사용
while let name = nameOptionValue { }
Swift
복사
optional binding를 통해서 언래핑된 값은 해당하는 영역에서 forced unwrapping를 하지 않고 사용할 수 있습니다. 하지만, 해당하는 영역에서만 unwrapping된 값이기 때문에 영역을 벗어나면 동일하게 optional 값으로 취급받습니다.
예를 들자면, if let 으로 처리된 name은 if 코드 블럭 내부에서는 non-optional 값이지만, 그 외의 부분에서는 optional 값입니다.
optional binding를 작성할 시에 여러 형태가 나타날 수 있습니다.
// let말고 var로도 작성 가능 -> 이 때 값 수정 가능해짐(로컬에서만 영향있음)
if var name = nameOptionValue { }
// 한 줄에 여러 조건을 넣을 수 있음 -> 한 조건이라도 false면 { } 실행 못함
if let name = nameOptionValue, name != "" { }
// original 이름과 동일한 이름으로 짓기 가능
if let nameOptionalValue = nameOptionValue { }
// 위의 축약형
if let nameOptionalValue { }
Swift
복사
값이 없을 리 없는 변수, 상수를 매번 모든 곳에서 optional binding를 통해서 unwrapping 해주는건 귀찮은 일일겁니다. 따라서, 값이 결정된 후에 해당 변수, 상수에 항상 값이 있는 게 분명한 경우에는 액세스마다 언래핑을 하지 않는 방법이 있습니다. 바로 implicity unwrapped optional, 즉 선언 시에 타입 뒤에 ! 를 붙이는 겁니다.
해당 타입은 일반적인 optional이지만, 액세스 시에 래핑 해제할 필요없이 non-optional처럼 사용 가능합니다.
즉, 필요한 경우에 강제로 optional를 해제할 수 있는 권한이 부여된겁니다.
let assumedString: String! = "An implicitly unwrapped optional string."
Swift
복사
위의 코드에 있는 Implicity unwrapped optional 타입을 String 타입에 할당하게 되면 forced unwrapping를 통해서 String 타입이 됩니다. 반대로 String? 타입에 할당하면 optional String 타입이 됩니다.
implicity unwrapped optional도 일반 optional과 동일하게 값이 nil이라면 런타임 오류가 발생합니다. 따라서, 이런 문제를 방지하기 위해 언래핑 전에 if문을 사용해서 nil인지 확인하거나 optional binding를 통해서 non-optional 값을 빼와야 합니다.
하지만, 저렇게 할 바에는 애초에 nil이 될 가능성이 있다면 implicity unwrapped optional를 사용하지 않는게 좋습니다. 변수가 사용되는동안 한 번이라도 nil이 될 가능성이 있어서 이를 체크해야 한다면 그냥 optional 타입으로 사용하십쇼.
Type Aliases
현재까지 Swift에서 사용할 수 있는 다양한 타입들을 봤습니다.
하지만, 타입들이 해당 타입으로만 사용되지 않고 별칭을 정의하여 사용할 수도 있습니다.
상황에 맞게 기존 타입을 참조하려는 경우에 유용하며, original 타입을 사용하는 어디서든 typealias를 만들 수 있습니다.
아래 예시에서는 UInt16 타입을 AudioSample 이라는 이름으로 사용합니다. 해당 상황에서 UInt16보다는 AudioSample이라는 별칭으로 사용했을 때, 가독성이 좋기 때문에 별칭을 따로 지은겁니다. 따라서, 하단에 AudioSample.min은 UInt16.min과 동일한 의미를 가집니다.
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
Swift
복사
️ Error Handling
위에서 배운 Optional 타입은 강제 언래핑 시, 강제 언래핑하려는 변수, 상수가 nil 값을 가지고 있으면 런타임 에러를 발생시킵니다. 런타임 에러가 발생하면 앱이 강제 종료됩니다.
런타임 에러가 발생하는건 크리티컬한 문제입니다. 따라서, 우리는 프로그램 실행 중에 발생할 수 있는 오류에 대응할 수 있어야 합니다.
에러 핸들링을 사용해서 기본적 오류의 원인을 확인하고, 필요한 경우 오류를 프로그램의 다른 부분에 보낼 수 있습니다.
그러기 위해서는 에러가 발생할 수 있는 메서드, 함수에서 에러를 잡아서 적절하게 응답해줄 수 있는 상황이어야 합니다. 바로 throws 키워드를 사용해서 function이 에러를 throw할 수 있다는 걸 나타내면 됩니다.
func request(_ request: URLRequest) throws -> (Int, URLResponse)
Swift
복사
해당 함수를 호출하기 위해서는 try 키워드를 써야 합니다. 또한, do statement를 사용해서 새로운 scope를 생성해주어야 catch statement에서 에러를 잡을 수 있습니다.
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
Swift
복사
makeASandwich 메서드에서 outOfCleanDishes, missingIngredients 에러를 발생시키면 catch 블록에 있는 에러 관련 로직이 실행될 것이고, 정상적으로 실행이 잘된다면 eatASandwich 메서드가 실행될 겁니다.
이렇게 예측한 에러가 발생했을 때, 오류를 처리하는 방법도 있지만 런타임 시 추가 코드 실행 전에 필수 조건이 충족되지 않으면 유효하지 않다고 판단하여 코드 실행을 종료하고 앱을 종료시키는 방식도 있습니다.
바로 Assertion과 Precondition 입니다.
두 가지 모두 조건이 true라면 평소처럼 실행을 하고, 아니라면 코드 실행을 종료하고 앱을 강제 종료시킵니다. 각각의 메서드는 도움을 주는 부분이 다릅니다.
•
Assertion : 개발 중의 실수, 잘못된 가정을 찾는데 도움
•
Precondition : 프로덕션의 문제를 탐지하는데 도움
두 가지 모두 복구 가능하고, 예상되는 오류에서는 사용하면 안됩니다. 해당 메서드를 사용한다고 잘못된 조건이 발생하지 않는 방식으로 코드 설계가 될 순 없습니다.
대신, 유효한 데이터, 상태에서 잘못된 값이 발생했을 때 사용하면 좋습니다. 앱이 예측 가능하게 종료되기 때문에 문제를 쉽게 디버깅할 수 있고, 발생 즉시 실행이 중지되기 때문에 잘못된 상태로 인해 앱이 손상되는걸 막습니다.
그렇다면, 둘은 도움을 주는 부분이 다르다는 점에서만 차이가 있는걸까요?
그뿐아니라, 언제 체크를 하느냐가 다릅니다.
Assertion은 debug 빌드에서만 검사를 진행합니다. 따라서, production 빌드 시에는 assert 조건을 평가하지 않습니다. 프로덕션 성능에 영향을 주지 않고 사용할 수 있는거죠.
Precondition은 debug, production 모두에서 검사를 진행합니다.
해당 메서드는 이렇게 사용하면 됩니다.
let three = 3
// 조건, 조건이 false라면 print할 메시지
assert(three > 0, "0, 음수가 들어갔습니다.")
// 이미 조건이 false된 상태라면 사용
assertionFailure("문제 발생")
// 조건, 조건이 false라면 print할 메시지
precondition(three > 0, "0, 음수가 들어갔습니다.")
// 이미 조건이 false된 상태라면 사용
preconditionFailure("문제 발생")
Swift
복사
만약, unchecked mode(-0unchecked)로 컴파일한다면, precondition로도 검사가 안됩니다.
컴파일러가 전제 조건이 항상 참이라고 가정하고 그에 따라 코드를 최적화시키기 때문에 검사가 안되는 겁니다. 그렇다면 이러한 상황에서 최적화 설정과 관계없이 항상 실행을 중지시키고 싶다면 어떻게 해야할까요?
바로, fatalError()를 사용하는 겁니다.
프로토타이핑, 초기 구현 시에 fatalError를 사용해서 아직 구현되지 않은 기능이 실행되지 않게끔 막을 수 있습니다. fatalError는 최적화되지 않기 때문에 항상 실행을 중지할 수 있습니다.