제네릭이란 타입에 의존하지 않는 범용 코드를 작성할 때 사용한다

제네릭을 사용하면 중복을 피하고, 코드를 유연하게 작성할 수 있다

<aside> 💡 제네릭 문법

</aside>

제네릭 함수

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
   let tempA = a
   a = b
   b = tempA
}

위의 a,b 파라미터는 모두 동일한 Int 타입인 경우는 문제가 없음 하지만 각각 Double, String 처럼 다른 타입일 경우 오류 발생 하므로 반드시 같은 타입 끼리만 구현 가능하다는 것

→이럴때 사용하는것이 Generic

T는 Type Parameter 라고 부르는데 새로운 형식이 생성되는 것이 아니라 실제 함수가 호출될 때 해당 매개 변수의 타입으로 대체되는 Placeholder 임 <> 꺽쇠로 감싸는 이유는 새로운 타입이 아니니 이 타입이 존재하는지 찾지말라고 swift에게 말해주기 위함!

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numberOne = 10
var numberTwo = 5
 
var doubleOne = 10.5
var doubleTwo = 5.5
 
var stringOne = "A"
var stringTwo = "B"
 
swapTwoValues(&numberOne, &numberTwo)
print("\\(numberOne), \\(numberTwo)") // 5, 10  실제 함수 호출시 T는 Int타입
 
swapTwoValues(&doubleOne, &doubleTwo)
print("\\(doubleOne), \\(doubleTwo)") // 5.5, 10.5  실제 함수 호출시 T는 Double 타입
 
swapTwoValues(&stringOne, &stringTwo)
print("\\(stringOne), \\(stringTwo)") // "B", "A" 실제 함수 호출시 T는 String타입

print("\\(numberOne), \\(stringTwo)") // 서로 다른 타입을 파라밑로 전달하기때문에 에러!

이렇게 실제 함수를 호출할때 Type Parameter인 T의 타입이 결정된다.

제네릭 타입

위 내용처럼 제네릭을 이용한 함수는 제네릭 함수라고 하며, 제네릭은 함수이외에 구조체, 클래스, 열거형 타입에도 선언이 가능하다. 이것을 Generic Type 이라고 한다.

struct Stack<T> {
		let items: [T] = []

		mutating func push(_ item: T) { ... }
		mutating func pop() -> T { ... }
}

let stack1: Stack<Int> = .init()
let stack2: Stack<Int>.init()

제네릭 타입으로 Stack을 선언해주고, 제네릭 타입의 인스턴스를 생성해주었다. 제네릭 타입을 선언할때 마찬가지로 <>를 통해 어떤 타입으로 사용할 것인지를 명시해줘야한다. → 배열 생성때와 동일!! ( Array가 제네릭 타입이기 때문이다.)

let array1: Array<Int> = .init()
let array2 = Array<Int>.init()

타입 제약(Type Constraints)

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