Swift - Generic

이한솔·2023년 9월 30일
0

Swift 문법 🍎

목록 보기
29/32

Generic

제네릭이란 타입에 의존하지 않는 범용 코드를 작성할 때 사용한다.
제네릭을 사용하면 중복을 피하고, 코드를 유연하게 작성할 수 있다.
Array와 Dictoinary등 swift 표준 라이브러리의 대다수는 제네릭으로 선언되어 있다.

제네릭 선언

<> 안에 타입처럼 사용할 이름을 <T>처럼 Type Parameter로 넣어준다.
실제로 사용할 때 Type Parameter인 T의 타입이 결정된다.
보통 타입 파라미터 이름은 가독성을 위해 T나 V 같은 단일 문자나 Upper Camel Case를 사용한다.



제네릭 함수

일반 함수로 구현하면 정해둔 타입 외에 다른 타입으로 변경해서 사용 불가능하다.
타입마다 같은 내용의 함수를 여러개 구현해야 하는데, 제네릭을 사용하면 타입에 제한을 두지 않고 중복을 피할 수 있다.

// 일반 함수
func swapTwoInts(_ a: inout Int, _ b: inout Int){
    let temporaryA = a
    a = b
    b = temporaryA
}

var numberOne = 10
var numberTwo = 20

swapTwoInts(&numberOne, &numberTwo)

print(numberOne) // 출력값: 20
print(numberTwo) // 출력값: 10


// 제네릭
func swap<T>(_ a: inout T, _ b: inout T){
    let temporaryA = a
    a = b
    b = temporaryA
}


var one = "sol"
var two = "han"
swap(&one, &two)
print("\(one)\(two)") // 출력값: hansol


var a = 1
var b = 4
swap(&a, &b)
print("\(a), \(b)") // 출력값: 4, 1 

제네릭 함수 override

타입이 지정된 함수가 제네릭 함수보다 우선순위가 높아서 지정된 타입으로 함수를 실행할 경우 제네릭 함수가 아닌 지정된 함수가 실행된다.

func swap<T>(_ a: inout T, _ b: inout T){
    let tempA = a
    a = b
    b = tempA
    print("\(a)\(b) 제네릭 타입 함수 실행됨")
}


func swap(_ a: inout Int, _ b: inout Int) {
    let tempA = a
    a = b
    b = tempA
    print("\(a)\(b) int타입 함수 실행됨")
}


var a = "sol"
var b = "han"
swap(&a, &b) // 출력값: hansol 제네릭 타입 함수 실행됨

var c = 1
var d = 2
swap(&c, &d) // 출력값: 21 int타입 함수 실행됨


제네릭 타입

// 일반 구조체 타입
struct IntStack {
    var items = [Int]()
    
    mutating func push(_ item: Int){
        items.append(item)
        print(items)
    }
    
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

var intStack = IntStack()
intStack.push(2) // 출력값: [2]
intStack.push(5) // 출력값: [2, 5]
print(intStack.pop()) // 출력값: 5


// 제네릭 구조체 타입
struct Stack<T> {
    var items = [T]()
    
    mutating func push(_ item: T){
        items.append(item)
        print(items)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

// 제네릭 타입의 인스턴스를 생성 시 <>를 통해 어떤 타입을 사용할지 명시해줘야 함
var stringStack = Stack<String>()
stringStack.push("han") // 출력값: ["han"]
stringStack.push("sol") // 출력값: ["han", "sol"]

var doubleStack = Stack<Double>() 
doubleStack.push(3.2) // 출력값: [3.2]
print(doubleStack.pop()) // 출력값: 3.2
print(doubleStack.items) // 출력값: []


타입 제약

제네릭 함수와 타입을 사용할 때 특정 클래스의 하위 클래스나, 특정 프로토콜을 준수하는 타입만 받을 수 있게 제약을 둘 수 있다.

프로토콜 제약

func exampleFunction<T: SomeProtocol & SomeClass>(value: T) {
    // T는 SomeProtocol을 따르고 SomeClass를 상속해야 함
    // 함수 내용
}

// `==`이란 연산자는 Equatable이란 프로토콜을 준수할 때만 사용할 수 있다. 
// `T`라고 선언한 타입 파라미터 a, b가 Equatable 프로토콜을 준수하는 타입이 아닐 수도 있기 때문에 == 를 쓸 수 없다는 에러가 발생한다.
func isSameValues<T>(_ a: T, _ b: T) -> Bool {
    return a == b               // Binary operator '==' cannot be applied to two 'T' operands
}

// 이렇게 `T`를 Equatable 프로토콜을 준수하는 타입으로 제약하면 사용가능하다.
func isSameValues<T: Equatable>(_ a: T, _ b: T) -> Bool {
    return a == b               
}

클래스 제약

func exampleFunction<T: SomeClass>(value: T) {
    // T는 SomeClass를 상속해야 함
    // 함수 내용
}


제네릭 확장

struct Stack<T> {
    var items = [T]()
    
    mutating func push(_ item: T){
        items.append(item)
        print(items)
    }

}

// 확장하면서 새로운 제네릭을 선언하거나, 다른 타입 파라미터 사용 불가능
extension Stack<T> {

} // Error: Cannot find type 'T' in scope

extension Stack {
    mutating func pop() -> T {
        return items.removeLast()
    }
}

where절 사용

where를 사용해서 제약을 줄 수 있다.

extension Stack where T: Equatable {
    func isSameValues(_ a: T, _ b: T) -> Bool {
        return a == b               
    }
}

0개의 댓글