chatGPT가 생각하는 Optional

 

Optional Type

옵셔널 타입이란 값이 있을 수도 있고, 없을 수도 있는 타입을 말한다.

 

Swift에서는 값이 없음을 표시하기 위해 nil을 사용한다.

참조가 없음을 의미하는 다른 언어의 nil과는 사뭇 다르다.

 

var message: String = "My String"
message = nil // Error

 

Swift의 타입은 기본적으로 Non-Optional 타입이다. 즉, 반드시 값이 있어야 한다.

이러한 타입에 nil을 할당하면 컴파일 에러가 발생한다.

 

var myString1: String?
var myString2: Optional<String>

 

 

이것이 옵셔널 타입을 선언하는 방식이다.

위 두 변수는 이름만 다르고 같은 변수이다.

옵셔널 타입의 변수의 초깃값은 기본적으로 nil로 설정된다.

 

enum Optional<T> {
    case None
    case Some(T)
}

 

옵셔널 타입은 내부적으로 Enumertaion으로 정의한다.

 

우리가 어떤 변수를 옵셔널 타입을 선언할 때,

해당 변수에 nil을 할당했다면, None 값을 갖는 것이다.

해당 변수에 특정 값을 할당했다면, <T>에 대응하는 associated value를 갖게 되는 것이다.

 

 

Optional Type의 필요성

옵셔널은 런타임 오류를 방지하는 데에 그 목적이 있다.

옵셔널을 사용하면 컴파일 단계에서 변수 초기화 문제를 발견할 수 있다.

 

int i;
MyObject *m;

-(int)myMethodWithValue:(int)i {
    return i*2;
}

NSLog(@"Value: %d",[m myMethodWithValue:5]);

 

Objective-C에 이런 코드가 있다.

i와 m을 선언하고, 초기화는 하지 않는다고 가정하자.

이 코드를 실행하면 의도한 10(5 * 2)의 값이 아니라, 0의 값을 얻게 된다.

 

왜 why?

옵젝씨에서는 값을 초기화하지 않은 정수형 변수를 0으로 초기화하나 보다.

중요한 것은 에러조차 없이 이상하게 동작한다는 것이다.

이런 문제는 찾아내기 굉장히 어렵다.

 

var myString: String
print(myString) // ERROR: Variable 'myString' used before being initialized

 

같은 상황에서 Swift는 컴파일 에러를 띄워준다.

값이 필요하면 반드시 초기화를 하고,

값이 없을 수도 있는 경우에는 무조건 Optional 변수를 사용하라는 뜻이다. 참 따뜻하다.

 

 

Optional Unwrapping

옵셔널 타입의 변수를 사용하기 전에는 반드시 값이 있는지 없는지 검증해야 한다.

nil이 할당되어 있는 변수를 사용하려다가는 런타임 에러가 발생하면서 앱이 깨지고 만다. 와장창.

 

옵셔널 변수의 값을 얻어내는 것을 Optional Unwrapping이라고 한다.

두 가지 방법을 제시한다.

 

Forced unwrapping

// #1
var myString1: String?
myString1 = "test"
var test: String = myString1!
print(myString1) // test

// #2
var myString1: String?
myString1 = nil
var test: String = myString1! // Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
print(test)

 

옵셔널 변수의 끝에 느낌표(!)를 붙이면, 안에 있는 값을 강제로 얻을 수 있다.

프로그래밍에서 강제란... 보통은 하면 안 되는 것이다.

 

Swift에서도 같다.

웬만한 경우에는 Force unwrapping을 사용하지 않을 것을 권장한다.

 

#1번의 경우에는 정상적으로 unwrapping이 된다.

#2번의 경우에는 컴파일러가 myString1에는 값이 있을 것이라고 굳게 믿고 코드를 실행한다.

아뿔싸. 값이 없다. 런타임 에러를 만나고 앱이 깨진다.

 

Optional Binding

// #1
if let constantName = optionalVariable {
    statements
}

// #2
if var variableName = optional {
    statements
}

 

보다 평화롭고 권장되는 방법이다.

등호를 기준으로 오른쪽에 있는 옵셔널 변수의 값을 확인한다.

nil이 아닌 값이라면, constantName 변수에 할당한다.

if statement 범위에서 constantName 변수를 자유롭게 사용할 수 있다.

 

세간의 인식과는 다르게 `if let` 뿐만 아니라 `if var`도 가능하다.

 

var myString3: String?
myString3 = "Space, the final frontier"

if let tempVar = myString3 {
    print(tempVar) // "Space, the final frontier"
} else {
    print("No value")
}

print(tempVar) // Compile Error: Cannot find 'tempVar' in scope

 

실전 예시를 보면 이렇다.

함수 내부에서 tempVar는 잘 사용할 수 있다.

함수를 벗어나서 사용하려고 하면, 컴파일 에러가 발생한다. 당연하다.

 

if let tmp1 = optional1, let tmp2 = optional2, let tmp3 = optional3 {
    print(tmp1)
    print(tmp2)
    ...
}

 

위와 같이 하나의 optional binding line에 여러 개의 옵셔널 변수를 unwrapping하는 것도 가능하다.

이때 하나의 변수라도 nil이면, 모든 unwrapping에 실패한다.

 

if let myOptional = myOptional {
    print(myOptional)
} else {
    print("myOptional was nil")
}

 

같은 이름의 변수에 unwrapping 결과물을 할당하는 것도 가능하다.

변수 이름 작명에 고심하는 개발자를 배려하는 따뜻한 조처이다.

 

Optional types with tuples

var tuple1: (one: String, two: Int)?
var tuple2: (one: String, two: Int?)

 

튜플을 통째로 옵셔널로 만들 수 있다.

튜플의 원소 하나만을 옵셔널로 만들 수도 있다.

 

 

Optional Chaining

var tireSize = car?.tires?.tireSize

 

프로퍼티, 메서드, subscript를 호출할 때, 그것의 return 값이 optional인 경우 위와 같이 사용할 수 있다.

 

chaining된 값 중에서 하나라도 nil이면, 전체 변수가 nil이 된다.

모든 값이 nil이 아니라면, 전체 변수는 optional unwrapping된 값을 얻는다.

 

The nil coalescing operator

  optionalA ?? defaultValue

 

병합 연산자라고들 하는 것 같다. 발음은 코얼레싱에 가깝다.

 

삼항 연산자(tenary operator)와 유사한 방식으로 동작한다.

좌항의 값이 nil이라면 defaultValue가 할당된다.

nil이 아니라면 optional unwrapping된 값이 할당된다.

 

varr defaultName = "Jon"
var optionalA: String?
var optionalB: String?
optionalB = "Buddy"

var nameA = optionalA ?? defaultName // "Jon"
var nameB = optionalB ?? defaultName // "Buddy"

 

어렵지 않다.

optionalA는 nil이니까 defaultName.

optionalB는 nil이 아니니까 "Buddy"

 

var nameC1 = optionalA ?? defaultName
var nameC2 = optionalA != nil ? optionalA! : defaultName

 

위 두 문장은 같은 코드이다.

coalescing operator를 적극 활용하자.

 

 


참고

<Mastering Swift 5.3>, Jon Hoffman, 6th Edition

Chapter 3: Learning about Variables, Constants, Strings, and Operators

타입이란?

타입(Type)은 데이터의 형태이다.

Swift는 타입 안전(Type-Safe)언어이므로, 코드에서 사용하는 각각의 값에는 명확한 타입이 있어야 한다.

Swift 컴파일러는 이를 통해 타입 관련 오류를 미리 잡아낼 수 있다.

 

Swift에서 타입의 종류는 크게 2가지 뿐이다.

명명된 타입 / 복합(명명되지 않은) 타입

 

명명된 타입(Named Types)

정의될 때 커스텀 이름을 부여할 수 있는 타입.

- Class: 참조 타입(reference type)

- Struct: 값 타입(value type)

- Enum: 값 타입(value tupe)

- Protocol: 메서드, 프로퍼티 등의 요구사항을 정의하는 '청사진'

 

Swift Standard Library에 포함된 기본 타입.

기본 타입(Primitive Types) 혹은 데이터 타입(Data Types)으로도 부른다.

대부분 내부적으로 구조체(Struct)로 구현되어 있다.

 

- Int: 정수

- Double, Float: 소수점을 포함하는 실수

- String: 문자열

- Character: 단일 문자

- Bool: true / false 논리값

 

- Optional: 값이 있을 수도 있고, 없을 수도 있는 상황을 처리하는 타입. Enum으로 구현되어 있다.

 

- Array: 순서가 있는 값들의 컬렉션 타입.

- Dictionary: 키-값 쌍으로 이루어진 컬렉션 타입.

- Set: 순서가 없고, 중복을 허용하지 않는 유일한 값들의 컬렉션 타입.

Array, Dictionary, Set 이것들은 모두 컬렉션 타입으로, 구조체를 사용하여 구현되어 있다.
따라서 값 타입의 특성을 갖지만, 예외적으로 'copy-on-write'를 통해 성능 최적화 되어 있다.

 

복합 타입 / 명명되지 않은 타입 (Compound Types)

정의된 이름이 없는 타입.

- Tuples: 여러 값을 그룹화하여 단일 복합 값으로 만들 수 있는 타입.

더보기

복합 타입은 명명된 타입과 복합 타입을 포함할 수 있다.

- (Int, (Int, String)): 복합 타입

- (Int): 명명된 타입 (한 개의 타입을 괄호로 묶는 것은 아무 효과 없다.)

- Function Types: 특정 함수의 매개변수 타입과 반환 타입.

더보기

Swift에서 함수는 일급 객체로 취급되므로, 함수 타입의 변수를 선언할 수 있다. 그 변수에 함수를 할당할 수 있다.

// 함수 타입의 변수에 함수 할당
func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}

var mathFunction: (Int, Int) -> Int = addTwoInts
let result = mathFunction(2, 3) // 5
// 함수가 다른 함수를 return
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

func multiply(_ a: Int, _ b: Int) -> Int {
    return a * b
}

func chooseMathFunction(_ operation: String) -> (Int, Int) -> Int {
    if operation == "add" {
        return add
    } else {
        return multiply
    }
}

let mathFunctionOne = chooseMathFunction("add")
let mathFunctionTwo = chooseMathFunction("multiply")
let resultOne = mathFunctionOne(4, 5) // 9
let resultTwo = mathFunctionTwo(4, 5) // 20

 

Swift에서 class와 struct의 공통점은 '데이터를 캡슐화'하고, '사용자 정의 데이터 타입을 생성'하는 것이다.

그러나 메모리 관리 측면에서 중요한 차이점이 있다.

 

1. Value Type과 Reference Type

Struct (Value Type):

  • struct는 Swift에서 값 타입(value type)이다.
  • 값 타입은 변수에 할당되거나 함수에 전달될 때 그 값이 복사된다.
  • 각각의 인스턴스는 고유한 데이터 복사본을 가지며, 다른 인스턴스와 독립적이다.
  • 메모리에서는 스택(stack) 영역에 저장되는 경우가 많다. 스택은 컴파일 시간에 크기가 결정되는 변수들을 저장하며, 메모리 할당 및 해제가 빠르다.

Class (Reference Type):

  • class는 참조 타입(reference type)이다.
  • 참조 타입은 변수에 할당되거나 함수에 전달될 때 실제 데이터가 아닌 메모리 주소(즉, 참조)가 전달된다.
  • 이러한 방식은 여러 변수가 메모리상의 동일한 데이터 인스턴스를 공유할 수 있게 한다.
  • 클래스 인스턴스는 힙(heap) 영역에 저장된다. 힙은 런타임에 크기가 결정되는 변수들을 저장하며, 스택에 비해 메모리 관리가 더 복잡하고 느릴 수 있다.

 

2. 메모리 관리: Stack vs Heap

Struct의 메모리 관리

  • 구조체 인스턴스는 스택에 할당되어 빠르고 자동으로 메모리가 관리된다.
  • 스택은 LIFO(Last In, First Out) 방식으로 작동한다.
    • 변수가 범위(scope)를 벗어나면 자동으로 메모리에서 제거된다.
    • 스택 기반 메모리 할당은 작은 데이터 구조에 적합하다.

Class의 메모리 관리

  • 클래스 인스턴스는 힙에 저장되어 더 유연하지만, 복잡하고 비용이 많이 드는 메모리 관리가 필요하다.
  • Swift에서는 ARC(Automatic Reference Counting)를 사용하여 클래스 인스턴스의 메모리를 관리한다.
    • 객체에 대한 참조가 더 이상 없을 때 시스템이 자동으로 메모리를 해제하는 방식

 

3. 성능 측면

Struct:

  • 값 타입의 복사는 대체로 간단한 데이터에 대해서는 매우 빠르지만, 크기가 큰 구조체의 경우 비용이 많이 들 수 있다.
  • 따라서 작은 데이터 구조에 적합하다.

Class:

  • 참조 타입은 메모리 주소만 전달되므로, 일반적으로 크기에 관계없이 복사 비용이 적다.
  • 그러나 참조 카운팅으로 인한 오버헤드가 있으며, 힙 할당/해제로 인해 성능 저하가 발생할 수 있다.

 

4. 크기 결정: Compile-time vs Runtime

Struct (Compile-time):

  • struct는 컴파일 시간에 크기가 결정된다.
  • 이는 struct가 값 타입이며, 그 값이 복사될 때 전체 내용이 메모리에 복사되기 때문이다.
  • 컴파일 시간에 struct의 크기를 결정하면, 스택 메모리에 효율적으로 할당하고 관리할 수 있다.

Class (Runtime):

  • class는 런타임에 크기가 결정된다.
  • 클래스 인스턴스는 힙에 저장되며, 인스턴스의 크기는 런타임에 객체가 생성될 때 결정된다.
  • 이는 클래스 인스턴스의 크기가 동적으로 변할 수 있고, 상속, 다형성 등으로 인해 컴파일 시점에서 정확한 크기를 결정하기 어렵기 때문이다.
더보기

컴파일:

  • 소스 코드가 실행 가능한 코드(바이너리)로 변환되는 과정.
  • 이 시점에는 프로그램이 실행되지 않고, 컴파일러는 소스 코드를 분석하고 최적화하여 실행 파일을 생성한다.
  • 컴파일 시간에는 실행 환경에 대한 정보가 제한적이므로, 주로 정적인 분석과 최적화가 이루어진다.

런타임:

  • 컴파일된 프로그램이 실제로 실행되는 시간.
  • 이때 메모리 할당, 사용자 입력 처리, 오류 관리 등 동적인 작업이 수행한다.
  • 런타임에는 프로그램이 실제 작동 환경에서 실행되므로, 많은 동적 요소들이 관여한다.

 

5. 메모리 {할당 ~ 접근 ~ 해제} 과정

Struct (Value Type) - 스택 할당

  1. 메모리 할당:
    • 구조체(Struct) 인스턴스가 생성될 때, 그 크기와 구조는 이미 컴파일 시점에 결정된다.
    • 실행 시점에 해당 구조체 변수가 범위(scope)에 들어오면, 스택 메모리에 공간이 할당된다.
    • 스택 메모리는 함수 호출과 함께 관리되며, 각 함수 호출에 대한 로컬 변수들이 순차적으로 스택에 쌓인다.
  2. 메모리 접근:
    • 스택에 저장된 변수는 빠르게 접근 가능하다.
    • 스택은 LIFO(Last In, First Out) 구조를 가지기 때문에 최근에 할당된 변수가 가장 먼저 접근된다.
  3. 메모리 해제:
    • 변수의 범위(scope)를 벗어나면, 해당 변수에 할당된 메모리는 자동으로 해제된다.
    • 예를 들어, 함수가 종료되면 그 함수의 로컬 변수들이 스택에서 제거된다.
    • 이 과정은 빠르고 효율적이며, 개발자가 직접 관리할 필요가 없다.

Class (Reference Type) - 힙 할당

  1. 메모리 할당:
    • 클래스(Class) 인스턴스가 생성될 때, 그 크기가 런타임에 결정되고 힙에 메모리가 할당된다.
    • 힙은 동적 메모리 할당을 위한 공간으로, 실행 시간에 크기가 결정되는 데이터를 저장한다.
    • 클래스 인스턴스에 대한 참조(주소)는 스택이나 다른 클래스의 일부로 저장될 수 있지만, 실제 데이터는 힙에 위치한다.
  2. 메모리 접근:
    • 힙에 저장된 객체는 주소를 통해 접근된다.
    • 이 주소는 변수가 스택에 저장되거나 다른 객체에 포함될 수 있다.
    • 힙 메모리 접근은 스택에 비해 상대적으로 느릴 수 있다.
  3. 메모리 해제:
    • Swift에서는 ARC(Automatic Reference Counting)를 사용하여 클래스 인스턴스의 메모리를 관리한다.
    • 인스턴스에 대한 모든 참조가 사라지면, 즉 참조 카운트가 0이 되면 ARC는 자동으로 해당 인스턴스의 메모리를 해제한다.
    • 메모리 누수를 방지하기 위해 강한 순환 참조(strong reference cycles)를 피하는 것이 중요하다.
    • 힙에서의 메모리 할당과 해제는 스택에 비해 비용이 많이 들고 복잡할 수 있다.

 

6. ARC(Automatic Reference Counting)에 대한 추가 설명과 예시

class Person {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John") // "John is being initialized"
// 여기서 John에 대한 참조 카운트는 1.

reference2 = reference1 // John에 대한 참조 카운트가 2가 된다.
reference3 = reference1 // John에 대한 참조 카운트가 3이 다.

reference1 = nil // John에 대한 참조 카운트가 2로 감소한다.
reference2 = nil // John에 대한 참조 카운트가 1로 감소한다.
reference3 = nil // "John is being deinitialized"
// 모든 참조가 제거되었으므로 John에 대한 인스턴스가 메모리에서 해제된다.

 

ARC는 강한 순환 참조(strong reference cycles)를 방지하기 위해,

강한(strong), 약한(weak) 또는 미소유(unowned) 참조에 대한 개념을 이해하는 것이 중요하다.

이러한 메커니즘을 통해 메모리 누수를 방지할 수 있다.

 

7. ARC의 참조 유형

1. Strong 참조

class Person {
    let name: String
    init(name: String) { self.name = name }
}

var person1: Person? = Person(name: "Alice")
var person2 = person1 // Strong 참조
// 이 시점에서 'Alice'에 대한 참조 카운트는 2이다.
  • 기본 참조 방식:
    • Swift에서는 변수가 클래스의 인스턴스를 참조할 때 기본적으로 strong 참조를 사용한다.
    • 이는 참조하는 인스턴스가 메모리에 유지되어야 함을 의미.
  • 작동 원리:
    • strong 참조가 있는 동안, ARC는 해당 인스턴스를 메모리에서 해제하지 않는다.
    • 인스턴스에 대한 strong 참조가 하나라도 있으면, 해당 인스턴스는 메모리에 유지된다.
  • 메모리 누수 위험:
    • strong 참조는 순환 참조(두 객체가 서로를 strong으로 참조)를 생성할 수 있으며, 이는 메모리 누수로 이어질 수 있다.

2. Weak 참조

class Apartment {
    let unit: String
    weak var tenant: Person?

    init(unit: String) { self.unit = unit }
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
unit4A?.tenant = john
// 'John'에 대한 참조는 weak이므로 참조 카운트는 증가하지 않는다.

// 여기서 Apartment 클래스의 tenant 속성은 weak 참조이다. 
// john이 unit4A.tenant에 할당되어도, 'John' 인스턴스의 참조 카운트는 증가하지 않는다.
// unit4A.tenant에 <- john을 할당 == unit4A.tenant가 -> john을 참조함
  • 옵셔널 참조:
    • weak 참조는 옵셔널 형태로 선언된다.
    • 즉, 참조된 인스턴스가 메모리에서 해제될 수 있으며, 그 경우 참조는 자동으로 nil이 된다.
  • 주 사용 시나리오:
    • 순환 참조를 피하기 위해, 두 객체 간의 관계에서 한 쪽이 다른 쪽보다 "덜 중요할" 때 사용된다.
    • 예를 들어, delegate 패턴에서는 delegate를 weak으로 선언하는 것이 일반적이다.
  • 참조 카운트 영향:
    • weak 참조는 참조 카운트를 증가시키지 않는다.
    • 따라서, weak 참조만이 남아있는 객체는 ARC에 의해 메모리에서 해제될 수 있다.

3. Unowned 참조

class CreditCard {
    let number: UInt64
    unowned let customer: Person

    init(number: UInt64, customer: Person) {
        self.number = number
        self.customer = customer
    }
}

var bob: Person? = Person(name: "Bob")
var card: CreditCard? = CreditCard(number: 1234567890123456, customer: bob!)
// 'bob' 인스턴스는 CreditCard에 의해 unowned 참조된다.

// 이 경우 CreditCard 클래스는 customer 속성을 unowned로 참조한다. 
// 'bob' 인스턴스는 CreditCard가 존재하는 한 메모리에 유지되지만, CreditCard 인스턴스는 'bob'이 메모리에서 해제되더라도 그 상태를 변경하지 않는다. 
// 'bob'이 메모리에서 해제된 후에 card의 customer에 접근하려고 하면 런타임 오류가 발생할 수 있다.
  • Non-Optional 참조:
    • unowned 참조는 non-optional 타입.
    • 참조된 인스턴스가 항상 존재한다고 가정한다.
    • unowned 참조는 참조된 인스턴스가 해제된 후에도 nil로 바뀌지 않는다.
  • 사용 상황:
    • unowned 참조는 두 객체가 서로에 대해 동등한 생명주기를 가지고 있거나,
      참조하는 객체가 참조된 객체보다 더 긴 생명주기를 가질 때 사용된다.
    • 순환 참조를 피하는 데 사용될 수 있으나, 참조된 객체가 해제된 후에는 사용할 수 없다.
  • 위험성:
    • unowned 참조를 사용할 때는 주의가 필요하다.
    • 참조된 객체가 해제된 후에 unowned 참조를 접근하면 런타임 오류가 발생할 수 있다.

 

더보기

'참조하다'와 '참조되다'의 개념

  • 참조하다 (Referencing):
    • 객체 A가 객체 B를 '참조한다'는 것은 A가 B를 가리키고 있음을 의미.
    • 즉, A는 B의 데이터에 접근할 수 있으며, B의 생명주기에 영향을 줄 수 있음.
  • 참조되다 (Being Referenced):
    • 객체 B가 '참조된다'는 것은 하나 이상의 객체가 B를 가리키고 있음을 의미한다.
    • B는 참조하는 객체들에 의해 메모리에 유지될 수 있다.

'참조된 객체가 해제되다'

  • 의미:
    • '참조된 객체가 해제된다'는 것은 그 객체가 더 이상 필요하지 않아 메모리에서 제거되었음을 의미한다.
  • 개발자의 역할:
    • Swift에서는 주로 ARC(Automatic Reference Counting)가 이 과정을 관리한다.
    • 개발자는 객체의 참조를 제거함으로써 ARC가 객체를 메모리에서 해제하도록 유도할 수 있다.
    • 예를 들어, 객체에 대한 모든 strong 참조를 nil로 설정하면, ARC는 참조 카운트가 0이 되었음을 감지하고 객체를 메모리에서 해제한다.

 


+ 24.02.04

컬렉션 타입(Array, Dictionary, Set)의 조금은 다른 점

대량의 데이터를 관리하는 경우, 값 타입은 매번 전체 값을 메모리에 저장해야 하는 문제가 있다. 느려진다.

특히 이런 문제를 마주할 가능성이 높은 컬렉션 타입은 값 타입이지만, 내부적으로 참조 타입을 사용한다.

즉, 메모리 관리를 위해 Stack과 Heap을 모두 사용한다.

 

1. 메모리 할당

  • 스택에서의 할당
    • 컬렉션 타입의 인스턴스가 생성될 때, 인스턴스의 메타 데이터는 스택에 할당된다.
    • 메타 데이터에는 컬렉션의 크기, 용량 등의 정보가 포함된다.
    • 기존 규칙에 따라, 스코프를 기준으로 즉시 할당된다.
  • 힙에서의 할당
    • 실제 데이터(예: 배열의 요소)는 힙에 할당된 참조 타입을 통해 관리된다.
    • 즉, 컬렉션에 데이터가 추가, 수정, 삭제되는 경우 힙에 할당된다.
    • ARC를 통해서도 관리된다.

2. 데이터 수정

  • Copy-On-Write(COW)
    • 컬렉션의 복사본이 수정되려 할 때, Swift는 해당 컬렉션이 다른 인스턴스와 내부 데이터를 공유하고 있는지 확인한다.
    • 공유되고 있다면, 수정 작업 수행 전에, 내부 데이터의 실제 복사본을 생성한다. (힙에 새로운 데이터 할당)
    • 불필요한 데이터 복사를 방지한다.
    • 이때, ARC로 새로운 할당을 추적한다.
    • Class의 그것과 같다.

3. 메모리 해제

  • 스택에서의 해제
    • 컬렉션 타입의 인스턴스가 스코프를 벗어나면, 스택에 저장된 메타 데이터는 자동으로 해제된다.
    • 빠르다. ARC 필요없다.
  • 힙에서의 해제
    • 컬렉션 타입의 인스턴스에 연결된, 실제 데이터는 ARC에 의해 관리된다.
    • 인스턴스에 대한 모든 참조가 사라지면(RC == 0), ARC는 힙에서 해당 데이터를 해제한다.

개발자가 사랑하는 라이트 모드

 

튜플 Tuple

튜플은 복수의 값을 하나의 그룹으로 합친 것이다.

그룹 내에 모든 값이 같은 타입일 필요는 없다.

 

데이터를 저장하는 간단한 방법으로, 클래스 혹은 구조체를 대체할 수 있다.

함수에서 return 되는 여러 개의 값, 타입이 다른 값 등을 저장하기에 안성맞춤이다.

또한 class에서 지원하는 기능을 지원한다. (computed property, instance method, ...)

 

아래와 같은 방법으로 선언한다.

  var team = ("Boston", "Red Sox", 97, 65, 59.9)

 

역으로, 하나로 묶여있는 튜플을 여러 개의 변수에 나누어 할당(decompose)할 수도 있다. 

var team = ("Boston", "Red Sox", 97, 65, 59.9)
var (city, name, wins, losses, percent) = team

 

튜플의 값을 인덱스로 가져올 수 있다. 배열의 subscript 방식과는 형태가 다르다.

var team = ("Boston", "Red Sox", 97, 65, 59.9)
var city = team.0 // "Boston"
var name = team.1 // Red Sox"
var wins = team.2 // 97
var losses = team.3 // 65
var percent = team.4 // 59.9

 

지금까지 우리가 다룬 것은 unnamed tuple이다. value에 대응하는 이름이 없지 않은가 ?

즉, named tuple을 만드는 것도 가능하다.

이 방법을 사용하면 decomposition 단계를 건너뛸 수 있다.

이름으로 값에 접근하는 것 또한 가능하다.

var team = (city:"Boston", name:"Red Sox", wins:97, losses:65, percent:59.9)

print(team.0) // "Boston"
print(team.city) // Boston"

 

열거형 Enumeration

열거형은 관련된 타입끼리 묶어서, type-safe한 방법으로 활용하기 위한 데이터 타입이다.

줄여서 Enum이라고도 부른다. 이넘 ~

 

일반적으로 열거형은 name, raw type, member(==member value), raw value로 구성된다.

아래는 열거형의 기본적인 구조이다. (name, member 만을 활용)

enum Planets { 		// name, 대문자로 시작해야 한다.
    case mercury	// member, 대문자/소문자 상관 없지만 소문자 시작을 권장한다.
    case venus 		// member
    case earth 		// ...
    case mars
    case Jupiter
    case Saturn
    case Uranus
    case neptune
}

 

하나의 케이스에 여러 개의 member를 묶을 수도 있다.

enum Planets {
    case mercury, venus, earth, mars, jupiter
    case saturn, uranus, neptune
}

 

열거형의 값은 점 표현식으로 사용할 수 있다.

var planetWeLiveOn = Planets.earth // Planets 타입으로 추론
var furthestPlanet = Planets.neptune // Planets 타입으로 추론

 

타입 추론이 완료된 뒤에는, Planets 접두사 없이도 사용할 수 있다.

  planetWeLiveOn = .mars

 

열거형은 주로 값을 equals operator(==) 혹은 switch statement에서 비교 대상으로 사용하기에 적합하다.

// equals operator
if planetWeLiveOn == .earth {
    print("Earth it is")
}

// switch statement
switch planetWeLiveOn {
    case .mercury:    
        print("We live on Mercury, it is very hot!")
    case .venus:
        print("We live on Venus, it is very hot!")
    case .earth:
        print("We live on Earth, just right")
    case .mars:
        print("We live on Mars, a little cold")
    default:
        print("Where do we live?")
}

 

초기 타입 raw type, 초기 값 raw value

열거형의 raw type은 정수형에 한정되지 않는다. (String, Character, Int, Float(사용이 조금 어렵다), CustomType, ...)

열거형은 여러 개의 case가 있고, 각각은 raw type에 대응하는 raw value를 가질 수도 있다.

 

아래의 구조를 통해 직접 확인하자.

enum Devices: String { // raw type == String
    case MusicPlayer = "iPod" // rawValue == iPod
    case Phone = "iPhone" // rawValue == iPhone
    case Tablet = "iPad" // rawValue == iPad
}

 

저장된 rawValue는 점 표현식으로 사용할 수 있다.

print("We are using an \(Devices.Tablet.rawValue)") 
// "We are using an iPad"

 

raw type이 정수형인 경우에, auto-incremented 기능이 있다.

어떤 member에게 rawValue를 설정하면, 다음 member부터는 +1씩 커지는 rawValue를 부여한다.

enum Planets: Int {
    case Mercury = 1
    case Venus // 2
    case Earth // 3
    case Mars // ...
    case Jupiter
    case Saturn
    case Uranus
    case Neptune
}

 

rawValue를 설정하지 않으면 기본적으로 0부터 시작한다.

enum Planets: Int {
    case Mercury // 0
    case Venus // 1
    case Earth // 2
    case Mars = 100
    case Jupiter // 101
    case Saturn
    case Uranus
    case Neptune
}

연관 값 associated values

다소 헷갈리는 개념이다.

 

초기 값으로 여러 개의 값을 보관하려면 콤마로 구분하는 게 고작이다.

이 값을 활용하려면 콤마를 기준으로 파싱해 주어야 한다.

 

반면 연관 값은 더 많은 정보를 저장할 수 있게 해준다.

연관 값은 어떤 타입도 가능하며, member 사이에서도, member 내에서도 타입이 달라도 된다.

 

구조부터 즉시 확인해보자.

// raw value
enum AppleDevice: String {
    case iPhone = "X, 256GB"
    case iMac = "27, Pro, 300만원"
    case macBook = "Air, 1kg, 150만원"
}

// associated value
enum AppleDevice {
    case iPhone(model: String, storage: Int) // named tuple
    case iMac(size: Int, model: String, price: Int)
    case macBook(String, Int, Int) // unnamed tuple
}

 

위의 구조를 확인해 보면, 연관 값을 갖는 열거형에는 raw type이 없다.

unnamed associated values / named associated values 형태로 나뉜다.

 

연관값은 변수를 선언할 때, member의 인자로 값을 전달한다.

주로 switch statement를 활용해 연관 값을 비교하는 형태로 쓰인다.

 

아래에서 연관 값의 용례를 확인해보자.

var gift = AppleDevice.iPhone(model: "X", storage: 256)

switch gift {
case .iPhone(model: "X", storage: 256):
    print("iPhone X and 256GB")
case .iPhone(model: "X", _): // 와일드카드 패턴 사용 가능
    print("iPhone X")
case .iPhone: // 연관값 생략 가능
    print("iPhone")
case .iPhone(let model, let storage): // 블록 내부에서 연관값을 사용할 땐 상수로 바인딩 // 값을 변경할 때는 var 로 변경가능
    print("iPhone \(model) and \(storage)GB")
case let .iMac(size, model, price): // 모든 연관값을 동일한 형태로 바인딩한다면, let 키워드를 열거형 케이스 앞에 표기하는 것도 가능
    print("iMac \(size), \(model), \(price)")
}

 

마지막으로 열거형의 raw type이 정수형이 아니더라도 member끼리 비교하는 방법이 있다.

열거형이 Comparable 프로토콜을 따르게 하면 된다.

enum Grades: Comparable {
    case f
    case d
    case c
    case b
    case a
}

let acceptableGrade = Grades.c
let testOneGrade = Grades.b

if  testOneGrade < acceptableGrade {
    print("Grade is unacceptable")
} else {
    print("Grade is acceptable")
}

// 출력: "Grade is acceptable"
// 마치 Int의 auto-incremented처럼 f < d < c < b < a의 위계가 생기는 듯하다. rawValue에 접근하면 컴파일 에러가 발생한다.

 

연산자 Operator

할당 assignment

let x = 1
var y = "Hello" 
a = b

비교 Comparison

2 == 1 //false, 2 does not equal 1
2 != 1 //true, 2 does not equal 1
2 > 1 //true, 2 is greater than 1
2 < 1 //false, 2 is not less than 1
2 >= 1 //true, 2 is greater or equal to 1
2 <= 1 //false, 2 is not less or equal to 1

산술 Arithmetic

var x = 4 + 2 //x will equal 6
var x = 4 – 2 //x will equal 2
var x = 4 * 2 //x will equal 8
var x = 4 / 2 //x will equal 2
var x = "Hello " + "world" //x will equal "Hello World"

 

나머지 Remainder

var x = 10 % 3 //x will equal 1 
var x = 10 % 6 //x will equal 4

복합 Compound

var x = 6
x += 2 //x now is 8
x -= 2 //x now is 4
x *= 2 //x now is 12
x /= 2 //x now is 3

범위 closed range / half-open range

// closed range
for i in 1...3 {
    print("Number: \(i)")
}

// Number: 1
// Number: 2
// Number: 3

// half-open range
for i in 1..<3 {
    print("Number: \(i)")
}

// Number: 1
// Number: 2

삼항 ternary conditional operator

// 형태: (boolValue ? valueA : valueB)

var x = 2
var y = 3
var z = (y > x ? "Y is greater" : "X is greater") // z equals "Y is greater"

 

논리 Logical

// NOT
var x = true
var y = !x //y equals false

// AND
var x = true
var y = false
var z = x && y //z equals false

// OR
var x = true
var y = false
var z = x|| y //z is true

 

 

참고

<Mastering Swift 5.3>, Jon Hoffman, 6th Edition

Chapter 3: Learning about Variables, Constants, Strings, and Operators

 

https://onelife2live.tistory.com/13

변수(variables)와 상수(constants)

// identifier: name, age
// type: String, Int
// value: "Sally", 17
// var, let은...?

var age: Int = 17 // 변수
let name: String = "Sally" // 상수

age = 144 // now age is 144
name = "Compass" // Cannot assign to value: 'name' is a 'let' constant

 

변수와 상수는 identifier, 그리고 특정 타입의 value로 구성된다.

차이는 있다. 값을 할당한 이후에 상수의 값은 수정할 수 없고, 변수의 값은 수정할 수 있다.

 

변수와 상수의 이름(identifier)을 설정할 때는 몇 가지 규칙이 있다.

불가: 공백, 수학 기호, 화살표, 특정 유니코드, 박스 문자(⏌,┤), 맨 앞에 오는 숫자, Swift keywords(var, let, func, ...)

 

변수와 상수 정의하기

변수와 상수는 사용하기 전에, 반드시 정의해야 한다.

Swift keywords를 참고하면, 변수는 var, 상수는 let이라는 키워드를 앞에 붙여야 한다.

 

기본적으로 상수로 선언하는 것을 권장한다.

값을 바꿔야 하는 명확한 이유가 있다면 변수로 선언한다.

안전한 프로그램 실행을 위해 중요하며, 값이 바뀌지 않는 변수가 있다면 Xcode가 컴파일 단계에서 경고한다.

더보기

정의(definition)와 선언(declaration)의 차이

 

가장 큰 차이는 `메모리를 할당하는가?` 이다.

선언은 이름을 밝히는 것이다. 메모리를 할당하지 않는다.

정의는 선언된 변수/상수를 설명한다. 값이나 함수에 대한 내용을 포함한다. 메모리를 할당한다.

 

그러나 Swift는 둘을 구분하지 않는다.

더 정확히는 선언과 동시에 정의가 되기 때문에, Swift는 선언(+정의)하다로 용어를 통일한다.

따라서 아래에서도 선언하다 만을 사용하겠다. (*타입을 정의하다 는 OK)

참고: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/

변수와 상수는 한 줄에 하나씩 선언해도 되고, 여러 개를 선언해도 된다.

// Constants
let freezingTemperatureOfWaterCelsius = 0
let speedOfLightKmSec = 300000

// Variables
var currentTemperature = 22
var currentSpeed = 55

// Constants
let freezingTemperatureOfWaterCelsius = 0, speedOfLightKmSec = 300000

// Variables
var currentTemperature = 22, currentSpeed = 55

 

타입(Type)

타입 안정성 (Type Safety)

스위프트는 type-safe 언어이기 때문에, 변수/상수에 값을 저장하기 전에, 타입을 정의해야 한다.

정의된 타입과 다른 타입의 값을 할당하려고 하면, 컴파일 단계에서 에러를 찾아낸다.

vat integerVar = 10
integerVar = "My String" // Cannot assign value of type 'String' to type 'Int'

 

타입 추론 (Type inference)

컴파일러는 초기값을 근거로, 변수/상수의 타입을 추론할 수 있다.

이는 우리가 변수/상수를 선언할 때 타입을 생략할 수 있게 해준다.

var a = 1 	// Int
var b = 1.0 	// Double
var c = 3.14 	// Double
var d = "h" 	// String
var e = "hello" // String
var f = true 	// Boolean

// Float, Character로는 추론하지 않는다.

 

Swift는 타입 추론과 타입을 명시하는 것 중에서 하나만 선택할 것을 권장한다.

초기값이 있으면 타입 추론을, 초기값이 없을 때만 타입을 명시하는 것이 좋다.

var leastNumOfHamburger = 3 // 초기값 있으면 타입 추론 // Int
var randomVal: Int // 초기값 없으면 타입 명시

 

숫자형 타입(Numeric types)

정수형 타입(integer types)

64-bit 시스템에서, 정수형 타입 종류별, 표시 가능 범위

정수형 타입은 signed(+, -, 0)와 unsigned(+, 0)로 나뉜다.

signed 타입은 부호를 표시하기 위해 하나의 비트를 사용하기 때문에, unsigned 타입보다 표시 가능 범위가 좁다.

기본적으로 Int, 혹은 UInt 타입을 사용하는 것을 권장한다.

범위 확인은 다음과 같이 가능하다.

UInt8.max // 255
Int8.max // 127
Int8.min // -128

 

정수형 타입은 10진수, 2진수, 8진수, 16진수 형태로 표현할 수 있다.

변수/상수의 값에 적당한 prefix를 붙여주면, 컴파일러가 추론 가능하다.

var a = 95 	// 10진수(decimal)
var b = 0b1011111 // 2진수(binary): 0b
var c = 0o137 	// 8진수(octal): 0o
var d = 0x5f 	// 16진수(hexadecimal): 0x

print(a, b, c, d) // 95, 95, 95, 95

 

가독성을 위해 정수형 값에 언더스코어(_)를 사용하는 것이 허용된다.

컴파일러는 이 언더스코어를 무시한다.

let speedOfLightKmSec = 300_000 // 300000

 

정수형 타입에는 isMultipe(of:) 메서드가 있다.

해당 변수의 값이 of의 인자로 나누어 떨어지는지 확인할 수 있다.

가독성 측면에서 권장한다.

let number = 4

// 직접 나누기
if number % 2 == 0 {
    print("Even")
} else {
    print("Odd")
}

// 메서드 사용하기
if number.isMultiple(of: 2) {
    print("Even")
} else {
    print("Odd")
}

 

소숫점 타입(Floating-point, Double)

Swift의 소숫점 타입은 Float 그리고 Double이 있다.

Float: 32-bit 길이의 값을 표현한다. (Float16, Float32, Float64, Float80이 기본적으로 지원된다.)

Double: 64-bit 길이의 값을 표현한다.

 

Double을 사용하는 것을 권장한다. (Float은 특별한 이유가 있는 경우에 사용한다.)

상술하였듯이, Swift의 컴파일러는 기본적으로 소숫점 타입을 Double로 추론한다.

 

십진법 기준 Double은 최소 15자리수를 표현할 수 있으며, Float은 적게는 6자리수까지 표현 가능하다.

따라서 값을 표현하는 정확도에서 차이가 발생한다.

let f: Float = 0.111_111_166 + 0.222_222_222 // 0.3333334
let d: Double = 0.111_111_166 + 0.222_222_222 // 0.333333388

정수 타입과 소숫점 타입의 연산

정수 타입과 소숫점 타입의 연산은 불가능하다.

다시 말해, Swift는 정수와 소숫점 타입을 구분한다.

var a: Int = 3
var b: Double = 0.14
car c = a + b 	// operator '+' cannot be applied to operands of type Int and Double

 

따라서 하나의 타입으로 통일해 주어야 하는데, 보통은 정확도가 높은(더 많은 메모리를 사용하는) 타입으로 맞춰준다.

아래와 같이 타입을 바꿔주는 Double()과 같은 함수를, convenience initializer라고 부른다.

var a: Int = 3
var b: Double = 0.14
var c = Double(a) + b

 

 

불리언 타입(Boolean type)

불리언 타입의 값은 true, false 두 가지가 있다.

불리언 타입은 일반적으로 조건문(conditional statement)에 사용된다. (if, while, guard, ...)

let isSwiftCool = true
var isItRaining = false

if isSwiftCool {
    print("YEA, I cannot wait to learn it")
}
if isItRaining {
    print("Get a rain coat")
}

// 출력: "YEA, I cannot wait to learn it"

 

불리언 값의 부정문(true <-> false)을 표현하는 두 가지 방법이 있다.

isItRaining = !isItRaining // 값을 알고 있는 경우 사용한다.
isItRaining.toggle() // 값을 모를 경우 사용한다. 가독성에도 좋다고 한다. 가독성은 잘 모르겠다...

 

 

문자열 타입(String type)

문자열은 문자의 집합(collection of characters)으로 정의된다.

문자열은 아래와 같이 표현한다.

// 한 줄로 선언
var stringOne = "Hello"

// 여러 줄로 선언, 여러 줄 안에 한 줄 quote를 사용할 수도 있음.
var multiLine = """
This is a multiline string literal.
Jon says, "multiline string literals are cool"
"""

 

문자열은 순서를 갖는다. (ordered collection)

따라서 다음과 같은 방법으로 문자열에 포함된 문자를 순서대로 출력할 수 있다.

var stringOne = "Hello"

for char in stringOne {
    print(char)
}

stringOne.map {
    print($0)
}

// H
// e
// l
// l
// o

 

서로 다른 문자열을 합칠 수도 있다.

var stringA = "Apple"
let stringB = "Pen"
let string = "PineApplePen"

var stringC = stringA + stringB // stringC now is "ApplePen"
stringA += string // stringA now is "ApplePineApplePen"

 

변수를 문자열 안에 포함하는 문자열 보간법(String Interpolation)도 가능하다.

var stringA = "Jon"
var stringB = "Hello \(stringA)" // Hello John

 

Swift 5.1부터는 raw string을 생성할 수 있게 됐다.

문자열 안에서 double quote를 표현하고 싶으면 다음 방법을 사용하면 된다.

// 기존 double quote 표현 방식
let str = "The main character said \"hello\""

// raw string을 활용한 방식
let str1 = #"The main character said "hello""#

// raw string을 활용한 문자열 보간법
let ans = 42
var str2 = #"The answer is \#(ans)"#

 

문자열 메서드

// 소문자 / 대문자로 변환, lowercased() / uppercased()
var stringOne = "hElLo"
print("Lowercase String: \(stringOne.lowercased())")  // hello
print("Uppercase String: \(stringOne.uppercased())")  // HELLO

// 빈 문자열인지 확인, isEmpty() -> Bool
let stringOne = "You are the one"
let stringTwo = ""
print(stringOne.isEmpty) // false
print(stringTwo.isEmpty) // true

// 문자열 치환, replacingOccurrances(of:)
var stringOne = "one,to,three,four"
var stringTwo = stringOne.replacingOccurrences(of: "to", with: "two")
print(stringTwo) // one, two, three, four

 

부분 문자열(substring)

문자열 중에 일부를 추출해서 부분 문자열로 만들 수 있다.

주의할 점은 부분 문자열은 Substring의 인스턴스이다. String과는 다른 타입이다.

 

또한 부분 문자열은 임시적으로 사용하는 타입이다.

원본 문자열의 메모리를 공유하고 있기 때문에, 원본 문자열이 바뀌면 부분 문자열도 바뀐다.

따라서 값을 제대로 저장하려면 String으로 변환해야 한다.

다음과 같은 방법으로 부분 문자열을 만들 수 있다.

var path = "/one/two/three/four"

// 시작 인덱스와 끝 인덱스를 만든다, 이때 각각 변수는 String.index 타입이다. 정수형과는 다르다.
let startIndex = path.index(path.startIndex, offsetBy: 4)
let endIndex = path.index(path.startIndex, offsetBy: 14)

// 만든 인덱스로 subscripting 한다
let sPath = path[startIndex ..< endIndex] // Substring: "/two/three"
let newStr = String(sPath) 		// String: "/two/three"

// 한 쪽 범위가 열린 subscripting도 가능하다
path[..<startIndex] 	// String.SubSequence: "/one"
path[endIndex...] 	// String.SubSequence: "/four"

// 문자열(String)의 시작과 끝에 있는 문자(Character)를 추출할 수 있다. Optional 임을 유의하자.
path.first // Optional<Character>: "/"
path.last // Optional<Character>: "r"

// count 프로퍼티
path.count // 19
더보기

String.SubSequence는 SubString의 typealias이다.
즉 같은 말이다.

일반적으로는 SubString을 사용하는 듯하다.

 

 

 


참고

<Mastering Swift 5.3>, Jon Hoffman, 6th Edition

Chapter 3: Learning about Variables, Constants, Strings, and Operators

주석

크게 single-line comments와 multi-line comments로 나뉜다.

전자는 함수 내부에서, 바로 다음에 오는 코드 라인을 설명한다.

후자는 함수 바깥인 위쪽에서, 함수에 대한 정보를 제공한다.

 

보통 다음과 같은 구조로 사용한다.

parameter, returns, throws 등 형식을 잘 지키면 주석이 알아서 하이라이트 해준다.

/**
 The myAdd function will takes two integers, add them together and return the sum
 
 - parameter first: The first integer to add
 - parameter second: The second integer to add
 - returns: The sum of the two integers
 - throws: Our error
 */

func myAdd(first: Int, second: Int) -> Int {
    // add the two integers together
    let sum: Int = first + second
    return sum
}

myAdd(first: 5, second: 10)

 

함수 부분을 option 키를 누른 채로 클릭하면, 다음과 같이 세부 내용을 확인할 수 있다.

 

세미콜론

문장 끝에 세미콜론을 붙이는 것은 선택사항이다.

그러나 붙이지 않는 것을 강력히 권장한다.

 

괄호 ()

함수의 조건절에 괄호를 붙이는 것은 선택사항이다.

일반적으로 조건이 하나이면 괄호를 사용하지 않고, 조건이 여러 개이면 각각 괄호를 사용하는 것을 권장한다. 보기 좋으니까.

let x = 1
let y = 2

// 조건이 1개
if x == 1 {
    print("x == 1")
}

// 조건이 여러 개
if (x == 1) && (y == 2) {
    print("x == 1 and y == 2")
}

 

중괄호 {}

Swift는 Conditional statement(if, while, switch, guard, ...)를 중괄호로 감싸야 한다.

// Swift
if x == 1 {
  print("x == 1")
}

// Python
if x == 1:
    print("x == 1")

 

안전 장치

조건절에서 변수에 대한 할당이 불가능하다.

시도하면 에러가 발생한다.

 

 

참고

<Mastering Swift 5.3>, Jon Hoffman, 6th Edition

Chapter 1: Taking the First Steps with Swift

탄생

Swift는 애플이 만든 프로그래밍 언어이다.

2014 WWDC에서 최초로 깜짝 발표했다.

애플 생태계 소프트웨어를 위한 언어로 탄생했다.

 

버전

현재 기준(24.01.11) Swift 5.9.2 버전까지 나왔다.

각 버전별 주요 업데이트는 다음과 같다.

Swift 2: 오픈 소스 프로젝트로 전환했다.

Swift 3: 플랫폼 간 호환이 가능케 했다. (예컨대 macOS에서 작성한 코드를 Linux에서 작동시킬 수 있다.)

Swift 5: ABI(Application Binary Interface)의 안정화가 이루어졌다. Swift 5 이후 컴파일러는 ABI를 공유하기 때문에, 어떤 버전에 대해서도 실행 가능하다.

Swift 5.1: LSP(Language Server Protocol)이 도입되었다. 다른 플랫폼의 IDE에서도 구문 하이라이트, 자동완성, 툴팁 등의 기능을 작동케 하는 도구이다.

 

특징

애플은 Swift를 처음 소개할 때 Objective-C without the C 라고 말했다.

그러나 반은 맞고, 반은 틀리다고 한다.

 

장점

- Swift는 Javascript, Python과 같은 스크립트 언어가 아니라, C와 같은 컴파일 언어이다. LLVM 컴파일러를 통해 기계어로 번역된다. 그만큼 빠르다.

- 컴파일 시간에 오류를 잡아내기 때문에, 런타임 오류가 줄고, 앱의 안정성이 높다.

- 동시에 스크립트 언어의 작성 편의성을 최대한 가져왔다.

- 코드 작성에 있어 안전 장치가 많다.

- 함수형, 객체 지향, 프로토콜 지향 프로그래밍이 모두 가능하다.

 

단점

- 오픈 소스 프로젝트이지만, 사용처는 애플 생태계 소프트웨어로 극히 제한된다.

- concurreny에 대한 기능이 부족하다. GCD(Grand Central Dispatch, aysnc/await 등의 도구가 있다.

- 안전 장치가 매우 많다. 학습의 초기라면 작성이 어렵다.

- 빠른 실행 시간과 작성 편의성을 얻은 대신, 컴파일 시간이 길다.

 

참고

<Mastering Swift 5.3>, Jon Hoffman, 6th Edition

Chapter 1: Taking The First Steps with Swift

+ Recent posts