02. Swift의 기초

LatteQ1·2021년 7월 30일
0

Udemy로 iOS 앱 개발 강의를 듣고 있긴 하지만, 습관적으로 기록을 남기기 위해 공식문서(docs.swift.org)를 조금 읽고 있다. 다음은 Swift의 기초에 관한 것들 중 눈에 띄는 몇가지이다.

Type Annotations

상수나 변수 선언에 있어서, 그것에 대입할 수 있는 데이터 타입을 미리 명시할 수 있다. 물론 미리 데이터 타입을 규정하지 않은 경우에도, 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

Semicolons

다른 프로그래밍 언어들과는 달리, Swift는 문장 끝에 세미콜론(;)을 반드시 쓸 필요는 없다. 단, 여러 문장들을 한 줄에 쓰고자 할 때는 세미콜론으로 구분해야 한다.

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

Integers

Int형(Integer) 데이터 타입은 C 언어에서의 Int형과 거의 동일하다. 여기서 더 나아가 Swift는 8-bit Unsigned Integers로써 UInt8이라는 데이터 타입을 표현할 수 있는데, 이 UInt8에는 minmax 속성이 있어서 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

Type Aliases

Swift는 데이터 타입에 대해서 Alias를 지정하는 것이 가능하다.

typealias sample = UInt16
var num = sample.min
print(num) // 0

Tuples 🖇

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

Optionals 🤡

Optional & nil

옵셔널(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 Statements and Forced Unwrapping

if문은 옵셔널과 nil 을 비교하여 옵셔널에 값이 포함되어 있는지 확인할 수 있게 한다. 만약 옵셔널에 값이 있다면 nil과 같지 않음으로 간주된다.

만약 옵셔널에 값이 포함되어 있다고 확신되면, 옵셔널 이름 끝에 느낌표(!)를 추가하여 값에 접근하는 것이 가능하다. 이것을 바로 옵셔널 값 강제 해체(forced unwrapping)라고 한다.

if notAmbiguous != nil {
    print("notAmbiguous has definite value of \(notAmbiguous!).")
}

Optional Binding

옵셔널 바인딩(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")
        }
    }
}

Implicitly Unwrapped Optionals

옵셔널을 만들기위해 타입 뒤에 물음표(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

위의 코드에서 assumedStringimplicitString 이 옵셔널이 아닌 String 타입이기 때문에 implicitString에 값을 할당하기 전에 강제로 언래핑 된다.

반면에 아래의 코드에서 optionalString 은 명시적 타입이 없으므로 기본적으로 옵셔널이다.

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

암시적으로 언래핑된 옵셔널이 nil 인데 래핑된 값에 접근하려고 하면 런타임 에러가 발생하는데, 이것은 값이 없는 옵셔널 뒤에 느낌표를 배치한 것과 같다.

Error Handling

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

Assertions & Preconditions

Assertion(주장)과 Precondition(전제)을 사용하는 것은 유효하지 않는 조건이 발생하지 않도록 개발하기 위함이다. Assertion은 오직 디버그 빌드에서 체크된다. 반면 Precondition은 디버그와 프로덕션 빌드 모두에서 체크된다. 즉, 프로덕션 빌드일 때 Assertion 내부의 조건은 실행되지 않는다.

Debugging with Assertions

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

Enforcing Preconditions

assert문과 마찬가지로 Swift 표준 라이브러리 중 precondition(_:_:file:line:)을 활용하여 Precondition을 작성할 수 있다. 사용법은 assert문과 동일하다. 보통 Precondition은 각 조건이 참인지 여부를 확실하게 체크하기 위해 사용한다.

profile
전직 시스템엔지니어, 현직 데이터엔지니어, 하지만 하고 싶은건 iOS 개발

0개의 댓글