Udemy로 iOS 앱 개발 강의를 듣고 있긴 하지만, 습관적으로 기록을 남기기 위해 공식문서(docs.swift.org)를 조금 읽고 있다. 다음은 Swift의 기초에 관한 것들 중 눈에 띄는 몇가지이다.
상수나 변수 선언에 있어서, 그것에 대입할 수 있는 데이터 타입을 미리 명시할 수 있다. 물론 미리 데이터 타입을 규정하지 않은 경우에도, Swift 언어는 알아서 해당 변수의 데이터 타입을 추론(infer)해낸다.
// variable에 데이터 타입을 직접 명시하는 경우
var welcomeMessage: String
welcomeMessage = "Hello"
print(welcomeMessage) // Hello
// variables에 데이터 타입을 직접 명시하는 경우
var red, green, blue: Double
green = 323333333333333312
print(green) // 3.233333333333333e+17
// constant 이름을 emoji로 선언하면서 value를 대입하는 경우
let 📱 = "iPhone"
print(📱) // iPhone
다른 프로그래밍 언어들과는 달리, Swift는 문장 끝에 세미콜론(;)을 반드시 쓸 필요는 없다. 단, 여러 문장들을 한 줄에 쓰고자 할 때는 세미콜론으로 구분해야 한다.
let cat = "🐱"; print(cat) // 🐱
Int형(Integer) 데이터 타입은 C 언어에서의 Int형과 거의 동일하다. 여기서 더 나아가 Swift는 8-bit Unsigned Integers로써 UInt8
이라는 데이터 타입을 표현할 수 있는데, 이 UInt8
에는 min
과 max
속성이 있어서 Int 타입의 최대/최소값을 바로 도출할 수 있다. 이때, UInt8
과 같이 범위가 미리 제한된 데이터 타입에는 반드시 해당 범위 안의 값만 대입할 수 있다.
// UInt8의 최대값과 최소값
let minValue = UInt8.min, maxValue = UInt8.max
print(minValue) // 0
print(maxValue) // 255
// UInt8의 최소값, Int8의 최대값
let cannotBeNegative: UInt8 = -1 // error
let tooBig: Int8 = Int8.max + 1 // error
한편, Int 타입은 Double()
함수를 활용하여 Floating-Point, 즉, 소숫점을 갖는 데이터 타입으로 쉽게 변경할 수 있다.
let three = 3, point = 0.14159
let pi = Double(three) + point
print(pi) // 3.14159
Swift는 데이터 타입에 대해서 Alias를 지정하는 것이 가능하다.
typealias sample = UInt16
var num = sample.min
print(num) // 0
튜플(Tuples)은 여러 값들을 하나의 묶음으로 만들어주는 기능을 수행한다. 이때 하나의 튜플은 반드시 하나의 데이터 타입으로 통일될 필요가 없다. 예를 들어 HTTP 상태 코드 중 (404, "Not Found")
가 아주 대표적인 튜플 예시라고 할 수 있다.
이때 이 HTTP 상태 코드는 "(Int, String)
의 타입으로 이뤄진 튜플"이라고 말할 수 있으며, 분해할당(decompose)을 통해 개별 값들에 대한 접근이 가능하다. 만약 분해할당 하기 싫은 값이 있다면 간단하게 underscore(_)를 통해 튜플에서 배제시킬 수도 있다.
// Tuple 선언
let httpError = (404, "Not Found")
// 분해할당을 통해 Tuple 내 value들을 세부 선언
let (code, msg) = httpError
print("The status code is \"\(code)\"")
// The status code is "404"
print("The status code is \"\(httpError.0)\"")
// The status code is "404"
print("The status message is \"\(msg)\"")
// The status message is "Not Found"
print("The status message is \"\(httpError.1)\"")
// The status message is "Not Found"
// Tuple 안에서 상태 메시지 value를 underscore로 배제
let (onlyThis, _) = httpError
print("The status code is \"\(onlyThis)\"")
// The status code is "404"
한편, 튜플의 각 값들에 대해서 분해할당이 아니라 딕셔너리(Dictionary)와 유사하게, 마치 key: value
형태로도 애초부터 선언할 수도 있다. 만약 이렇게 튜플을 선언할 경우, key
는 해당 튜플의 속성(attribuite)이 되므로 Tuple.key
와 같은 형태로 값을 호출할 수 있게 된다.
// 분해할당 하지 않고 Tuple 선언
let httpOk = (code: 200, msg: "OK")
print("The status code is \"\(httpOk.code)\"")
// The status code is "200"
print("The status code is \"\(httpOk.0)\"")
// The status code is "200"
print("The status message is \"\(httpOk.msg)\"")
// The status message is "OK"
print("The status message is \"\(httpOk.1)\"")
// The status message is "OK"
옵셔널(Optional)은 어떤 데이터 타입 조건에 맞는 값이 존재하는지 안하는지 불확정적일 때 사용할 수 있다. 이는 C 언어나 Objective-C 언어에는 존재하지 않는 개념이다. 그나마 Objective-C 언어에서 nil
이 리턴되는 상황이 옵셔널과 가장 유사하다.(달리 말하면 Swift의 옵셔널은 Objective-C의 nil
을 호환하기 위해 도출된 기능으로 보인다😎)
가장 대표적인 예는 String "123"이 Int 123으로 변환되는 경우를 생각할 수 있다. 데이터 타입이 명백하게 Int로 선언되지 않은 곳에 이 Int 123을 대입할 경우, Swift는 이것을 두고
"Integer로 보이긴 하지만 정확하지 않을 수도 있음"
의 뉘앙스로써, 해당 데이터 타입이 옵셔널하다고 추론(infer)하게 된다. 당연히, String "abc"를 Int로 변환한 값을 대입하는 시도와 같은 경우에서는 이러한 옵셔널 추론(infer)은 발동되지 않는다.
let possible = "123"
// Swift가 알아서 Optional하게 데이터 타입을 추론
let converted1 = Int(possible)
print(converted1) // Optional(123)
// 처음부터 Optional한 데이터 타입이라고 선언
let converted2: Int? = Int(possible)
print(converted2) // Optional(123)
또한, 이러한 원리를 이용하여 옵셔널한 데이터 타입이라고 선언된 변수에 nil
을 대입하는 것도 가능하다. 당연히, 데이터 타입이 옵셔널 하지 않은 변수에 대해서는 nil
을 대입할 수 없다. 만약, 옵셔널한 데이터 타입이라고 선언만 하고 값을 대입하지 않은 상태라면, 해당 변수의 값은 자동으로 nil
이 된다.
// Optional한 데이터 타입의 default value
var optionalThing: String?
print(optionalThing) // nil
// Optional한 데이터 타입에 nil 대입
var ambiguous: Int? = 404
print(ambiguous) // Optional(404)
ambiguous = nil
print(ambiguous) // nil
if문은 옵셔널과 nil
을 비교하여 옵셔널에 값이 포함되어 있는지 확인할 수 있게 한다. 만약 옵셔널에 값이 있다면 nil
과 같지 않음으로 간주된다.
만약 옵셔널에 값이 포함되어 있다고 확신되면, 옵셔널 이름 끝에 느낌표(!
)를 추가하여 값에 접근하는 것이 가능하다. 이것을 바로 옵셔널 값 강제 해체(forced unwrapping)라고 한다.
if notAmbiguous != nil {
print("notAmbiguous has definite value of \(notAmbiguous!).")
}
옵셔널 바인딩(Optional binding)은 옵셔널이 값을 포함하고 있는지 확인하고, 값이 있는 경우 그것을 임시 상수 또는 변수로 사용할 수 있게 한다. 이를 위해 바로 Forced Unwrapping의 방법 중 하나인 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")
}
}
}
옵셔널을 만들기위해 타입 뒤에 물음표(String?
)를 작성하는 대신에, 느낌표(String!
)로 암시적으로 언래핑된(implicitly unwrapped) 옵셔널을 작성한다. 옵셔널을 사용할 때 그 이름의 뒤에 느낌표를 위치시키는 것보다는, 옵셔널을 선언할 때 타입 뒤에 느낌표를 위치시키는 것이 더 권장된다.
암시적으로 언래핑된 옵셔널은 그것이 최초에 정의된 이후, 값이 존재하는 것으로 확인되고 그 이후 모든 시점에 존재한다고 가정할 수 있는 경우에 유용하다. 이러한 암시적으로 언래핑된 옵셔널은 클래스 초기화 중에 주로 사용된다.
암시적으로 언래핑된 옵셔널은 내부적으로 옵셔널이지만 옵셔널에 접근할 때마다 옵셔널의 값을 풀 필요없이 옵셔널이 아닌 값처럼 사용하는 것이 가능하다.
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
위의 코드에서 assumedString
은 implicitString
이 옵셔널이 아닌 String 타입이기 때문에 implicitString
에 값을 할당하기 전에 강제로 언래핑 된다.
반면에 아래의 코드에서 optionalString
은 명시적 타입이 없으므로 기본적으로 옵셔널이다.
let optionalString = assumedString
// The type of optionalString is "String?" and assumedString isn't force-unwrapped.
암시적으로 언래핑된 옵셔널이 nil
인데 래핑된 값에 접근하려고 하면 런타임 에러가 발생하는데, 이것은 값이 없는 옵셔널 뒤에 느낌표를 배치한 것과 같다.
Swift는 catch 절과 throws 키워드를 활용하여 에러 처리를 할 수 있도록 한다. 특히, 에러를 발생할 수 있는 함수를 호출할 때는 표현식 앞에 try 키워드를 붙이도록 한다.
func makeASandwich() throws {
// this function may or may not throw an error
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missSalt(let salt) {
goMarket(salt)
}
Assertion(주장)과 Precondition(전제)을 사용하는 것은 유효하지 않는 조건이 발생하지 않도록 개발하기 위함이다. Assertion은 오직 디버그 빌드에서 체크된다. 반면 Precondition은 디버그와 프로덕션 빌드 모두에서 체크된다. 즉, 프로덕션 빌드일 때 Assertion 내부의 조건은 실행되지 않는다.
Swift 표준 라이브러리 중 assert(_:_:file:line:)
을 활용하면 Assertion을 작성할 수 있다. 보통 true/false로 판단할 표현식과, false일 경우 출력할 메시지를 저 '_' 부분에 차례대로 작성한다.
// assert 사용
let age = -3
assert(age >= 0, "A person's age can't be less than zero")
// assertionFailure 사용
if age > 19 {
print("You can ride a car")
} else if age >= 0 {
print("You are a human")
} else {
assertionFailure("A person's age can't be less than zero")
}
assert문과 마찬가지로 Swift 표준 라이브러리 중 precondition(_:_:file:line:)
을 활용하여 Precondition을 작성할 수 있다. 사용법은 assert문과 동일하다. 보통 Precondition은 각 조건이 참인지 여부를 확실하게 체크하기 위해 사용한다.