본문 바로가기
Programming

Swift 고급 타입

by 나무수피아는 지식의 가지를 뻗어가는 공간입니다. 2025. 8. 4.
반응형

고급 타입 (Advanced Types)

이번 강의에서는 Swift의 고급 타입 기능들을 상세히 다루겠습니다. Swift는 기본 타입 외에도 튜플, 타입 별칭(typealias), 메타 타입(Type.Type), 그리고 제네릭(Generics)과 같은 고급 타입 시스템을 제공합니다. 이들은 복잡한 데이터 구조를 효율적으로 표현하고, 재사용 가능하며 유지보수가 쉬운 코드를 작성하는 데 매우 유용합니다. Swift 고급 타입을 이해하면 더 깔끔하고 안전한 코드 작성이 가능해져 실무에서 큰 도움이 됩니다.

1. 튜플 (Tuple)

튜플은 여러 개의 서로 다른 타입의 값을 하나로 묶을 수 있는 자료형입니다. 배열과 달리 튜플은 각 요소마다 타입과 위치가 고정되어 있으며, 이름을 붙일 수도 있습니다. 이를 통해 함수에서 여러 값을 한꺼번에 반환하거나, 서로 연관된 값을 그룹화할 때 편리하게 사용됩니다.

let person: (String, Int) = ("Alice", 30)
print("이름: \(person.0), 나이: \(person.1)")

// 이름 붙이기 (Named Tuple)
let user = (name: "Bob", age: 25)
print("이름: \(user.name), 나이: \(user.age)")

// 함수에서 튜플 반환하기
func getUser() -> (name: String, age: Int, isMember: Bool) {
    return ("Charlie", 28, true)
}

let userInfo = getUser()
print("이름: \(userInfo.name), 나이: \(userInfo.age), 회원 여부: \(userInfo.isMember)")

위 예제처럼 튜플은 요소에 이름을 붙여 가독성을 높일 수 있고, 함수에서 여러 값을 반환할 때 매우 유용합니다. 하지만 튜플은 고정된 크기와 타입을 가지므로, 동적으로 크기가 변하는 데이터를 다루기에는 적합하지 않습니다.

또한, 튜플은 구조체와 달리 메서드를 가질 수 없고 상속도 불가능하기 때문에 복잡한 데이터 모델링보다는 간단한 데이터 그룹핑에 적합합니다.

2. 타입 별칭 (typealias)

Swift에서 typealias는 기존 타입에 새로운 이름을 붙이는 기능입니다. 복잡한 타입명을 간결하게 바꾸거나, 의미 있는 이름을 부여하여 코드를 읽기 쉽게 만듭니다. 특히 클로저(Closure) 타입이나 긴 제네릭 타입을 다룰 때 유용합니다.

typealias Coordinate = (x: Int, y: Int)

let point: Coordinate = (x: 10, y: 20)
print("좌표: (\(point.x), \(point.y))")

// 복잡한 클로저 타입에 별칭 사용하기
typealias CompletionHandler = (Bool, Error?) -> Void

func fetchData(completion: CompletionHandler) {
    // 비동기 작업 후 콜백 호출
    completion(true, nil)
}

fetchData { success, error in
    if success {
        print("데이터 가져오기 성공")
    } else {
        print("오류 발생: \(error!)")
    }
}

위 코드에서 Coordinate라는 별칭으로 튜플 타입을 간단하게 표현하였고, 클로저 타입에도 별칭을 사용해 함수 매개변수를 명확히 하였습니다. 이렇게 별칭을 사용하면 코드를 더 직관적이고 유지보수하기 쉽게 만들 수 있습니다.

또한, API 설계 시 복잡한 타입을 명확히 표현하여 다른 개발자들과의 소통에도 큰 도움이 됩니다.

3. 메타 타입 (Type.Type)

Swift에서는 타입 자체도 하나의 값처럼 취급할 수 있는데, 이를 메타 타입이라고 합니다. 메타 타입은 특정 타입의 타입을 나타내며, 주로 런타임에 타입 정보를 얻거나 타입을 동적으로 다룰 때 사용합니다.

class Dog {
    class func sound() {
        print("멍멍")
    }
}

class Cat {
    class func sound() {
        print("야옹")
    }
}

let dogType: Dog.Type = Dog.self
let catType: Cat.Type = Cat.self

func makeSound(animalType: AnyObject.Type) {
    if let dog = animalType as? Dog.Type {
        dog.sound()
    } else if let cat = animalType as? Cat.Type {
        cat.sound()
    } else {
        print("알 수 없는 동물")
    }
}

dogType.sound()  // "멍멍"
catType.sound()  // "야옹"
makeSound(animalType: Dog.self) // "멍멍"
makeSound(animalType: Cat.self) // "야옹"

위 예제에서 Type.self는 클래스의 메타 타입 값을 나타내며, 이를 변수에 할당해 런타임에 타입에 따라 다른 동작을 하도록 구현할 수 있습니다. 메타 타입은 특히 의존성 주입이나 팩토리 패턴 구현, 타입 검사 등에 활용됩니다.

메타 타입을 활용하면 코드가 더욱 유연해지고, 컴파일 시 알 수 없는 타입 정보를 런타임에 처리할 수 있는 강력한 도구가 됩니다.

4. 제네릭 (Generics)

제네릭은 함수나 타입이 다양한 타입에서 동작할 수 있도록 일반화시킨 문법입니다. 제네릭을 사용하면 중복된 코드를 줄이고, 타입 안정성을 유지하면서 코드 재사용성을 극대화할 수 있습니다. Swift의 표준 라이브러리에서도 컬렉션 타입(Array, Dictionary 등)과 많은 함수들이 제네릭으로 구현되어 있습니다.

func swapValues(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 10, y = 20
swapValues(&x, &y)
print("x: \(x), y: \(y)")

var str1 = "Hello", str2 = "World"
swapValues(&str1, &str2)
print("str1: \(str1), str2: \(str2)")

// 제네릭 타입 예제: Stack 구조체 구현
struct Stack {
    private var items = [Element]()

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.popLast()
    }

    func peek() -> Element? {
        return items.last
    }

    var isEmpty: Bool {
        return items.isEmpty
    }

    var count: Int {
        return items.count
    }
}

var intStack = Stack()
intStack.push(1)
intStack.push(2)
print("스택 탑: \(intStack.peek() ?? -1)") // 2
print("스택 크기: \(intStack.count)")

var stringStack = Stack()
stringStack.push("Swift")
stringStack.push("Generics")
print("스택 탑: \(stringStack.peek() ?? "빈 스택")") // Generics

위 예제는 제네릭 타입 매개변수 TElement를 사용하여 다양한 타입에서 동작하는 swapValues 함수와 Stack 구조체를 구현한 예입니다. 제네릭은 타입에 의존적인 코드를 작성할 때 타입 안전성을 보장하면서 재사용 가능하도록 합니다.

또한, 제네릭에서는 타입 제약조건(Constraints)을 사용해 특정 프로토콜을 준수하는 타입만 허용하도록 제한할 수도 있습니다. 예를 들어 where 절을 사용해 제네릭 타입이 특정 프로토콜을 준수하도록 강제할 수 있습니다.

func findIndex(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let numbers = [1, 2, 3, 4, 5]
if let index = findIndex(of: 3, in: numbers) {
    print("3의 인덱스는 \(index)입니다.")
}

정리 및 마무리

  • 튜플(Tuple)은 여러 타입을 묶는 간단한 구조로 함수 다중 반환 등에 유용합니다.
  • 타입 별칭(typealias)은 복잡한 타입을 쉽게 표현하고 가독성을 높여 줍니다.
  • 메타 타입(Type.Type)은 타입 자체를 값으로 다뤄 런타임 타입 정보 처리에 활용됩니다.
  • 제네릭(Generics)은 코드 재사용성과 타입 안정성을 극대화하는 핵심 문법입니다.

Swift의 고급 타입을 잘 활용하면, 코드의 유연성과 유지보수성이 대폭 향상됩니다. 각 타입의 특징과 용도를 이해하고, 상황에 맞게 적절히 선택하는 것이 중요합니다. 다음 강의에서는 프로토콜과 고급 메모리 관리 기법에 대해 살펴보겠습니다.

반응형

'Programming' 카테고리의 다른 글

C 역사와 특징  (57) 2025.08.06
Swift 메모리 관리  (36) 2025.08.05
Swift 오류 처리  (46) 2025.08.03
Swift 확장과 접근 제어  (47) 2025.08.02
Swift 프로토콜  (37) 2025.08.01