Swift

[Swift] Generic

yujaehui 2024. 4. 15. 12:47

1. Generic이란?

Generic(제네릭)은 코드의 재사용성과 유연성을 극대화하기 위해 사용.

특정 타입에 국한되지 않고, 다양한 타입에서 동작할 수 있는 코드를 작성할 수 있도록 함.

Generic을 사용하면 코드의 중복을 줄이고, 타입 안정성을 유지하면서도 유연한 구조를 구현이 가능.


2. Generic의 동작 원리

Generic은 타입을 플레이스홀더로 정의하여, 실제 타입은 코드가 호출될 때 결정.

이렇게 하면 동일한 로직을 여러 타입에 대해 중복 작성할 필요가 없음.


3. Generic의 사용 예제

Generic 타입은 꺾쇠 괄호(<>)를 사용하여 선언.

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

var x = 10
var y = 20
swapValues(&x, &y)
print(x, y) // 20, 10

var str1 = "Hello"
var str2 = "World"
swapValues(&str1, &str2)
print(str1, str2) // "World", "Hello"
  • 여기서 T는 타입 매개변수로, 함수 호출 시 결정.

4. Generic을 사용하는 이유

  • 코드 중복 감소
    • 여러 타입에 대해 동일한 로직을 중복 작성하지 않아도 됨.
  • 타입 안정성
    • 컴파일 시점에 타입을 검증하므로, 런타임 오류를 방지할 수 있음.
  • 유연성
    • 다양한 타입에서 동작할 수 있는 코드 작성이 가능.

5. Generic 타입

Generic은 클래스, 구조체, 열거형에서도 사용할 수 있음.

Generic 클래스와 구조체

struct Stack<T> {
    private var elements: [T] = []

    mutating func push(_ item: T) {
        elements.append(item)
    }

    mutating func pop() -> T? {
        return elements.popLast()
    }
}

var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop()) // 2

var stringStack = Stack<String>()
stringStack.push("Swift")
print(stringStack.pop()) // "Swift"

Generic 열거형

enum Result<T> {
    case success(T)
    case failure(Error)
}

let successResult: Result<String> = .success("Data loaded")
let failureResult: Result<String> = .failure(NSError(domain: "", code: -1, userInfo: nil))

6. Generic 타입 제약 (Type Constraints)

Generic 타입을 사용할 때, 특정 프로토콜을 준수하는 타입으로 제한할 수 있음.

func printElements<T: Collection>(_ collection: T) {
    for element in collection {
        print(element)
    }
}

printElements([1, 2, 3]) // 배열
  • T: Collection은 T가 반드시 Collection 프로토콜을 준수해야 함을 나타냄.

7. Generic과 Protocol

Generic은 프로토콜과 함께 사용되어 더 강력한 기능을 제공.

프로토콜에서 제네릭을 사용하려면 연관 타입(Associated Type)을 선언.

protocol Container {
    associatedtype Item
    var items: [Item] { get set }
    mutating func addItem(_ item: Item)
}

struct IntContainer: Container {
    var items = [Int]()
    mutating func addItem(_ item: Int) {
        items.append(item)
    }
}

struct StringContainer: Container {
    var items = [String]()
    mutating func addItem(_ item: String) {
        items.append(item)
    }
}

8. Generic의 고급 기능

타입 제약을 결합하기

여러 제약 조건을 결합하여 특정 타입을 제한할 수 있음.

func compareValues<T: Equatable & Comparable>(_ a: T, _ b: T) -> Bool {
    return a == b
}

타입 매개변수와 연관 타입 사용

타입 매개변수와 연관 타입을 함께 사용 가능.

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

let index = findIndex(of: 5, in: [1, 2, 3, 4, 5])
print(index) // 4

9. 언제 Generic을 사용할까?

  • 여러 타입에 대해 동일한 로직을 구현할 때.
  • 타입 안정성과 유연성을 동시에 유지하고 싶을 때.
  • 코드 중복을 줄이고, 가독성을 높이고 싶을 때.

10. 결론

Generic은 Swift의 강력한 기능 중 하나로, 타입 안정성과 코드 재사용성을 극대화.

Generic을 적절히 활용하면 더욱 유연하고 효율적인 코드를 작성할 수 있음.

다만, 과도한 Generic 사용은 가독성을 저하시킬 수 있으므로, 필요한 경우에만 사용하는 것이 좋음.

'Swift' 카테고리의 다른 글

[Swift] associatedtype  (0) 2024.04.18
[Swift] Protocol  (0) 2024.04.18
[Swift] GCD vs Swift Concurrency: 스레드 관리와 성능  (0) 2024.04.15
[Swift] Swift Concurrency  (0) 2024.04.15
[Swift] GCD  (0) 2024.04.13