본문 바로가기
Swift

Swift: Generics

by songmoro 2023. 8. 29.
728x90

코드를 짜다보면, T라는 타입으로 인해 에러가 발생하는 경우가 한 번씩 있는데 실제로 T 타입을 검색해 봐도 정의되어있지 않죠.

placeholder 타입 T와 제네릭에 대해 알아보겠습니다.

 

Generics: Write code that works for multiple types and specify requirements for those types.(여러 유형에 대해 작동하는 코드를 작성하고 해당 유형에 대한 요구 사항을 지정하십시오.)

 

제네릭 코드는 유연하고, 재사용 가능한 함수와 타입을 작성할 수 있게 해줘요.

즉, 중복을 피하고, 명확하고 추상적인 방식으로 의도를 표현하는 코드를 작성할 수 있습니다.

 

제네릭은 스위프트에서 강하게 어필하는 기능으로 스위프트 라이브러리의 대부분은 제네릭 코드로 만들어졌다고 해요.

 

깨닫지 못했더라도,

배열이나 Dictionary 타입은 제네릭의 집합으로 Int를 보유하는 배열, String을 보유하는 배열 또는 다른 타입을 정의해 지정해서 배열을 만들 수 있는데, 이때 알게 모르게 사용하고 있던 게 제네릭입니다.

 

아래는 Int 값을 서로 교환하는 비 제네릭 함수입니다.

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

함수를 실제로 사용해 보면 a와 b가 교환되겠죠?

 

위 함수는 유용하지만 큰 단점이 있어요. 만약 swapTwoInts 함수를 통해 Int가 아닌 String 또는 Float와 같은 다른 타입의 값을 교환하고 싶으면 그때마다 아래처럼 새로운 함수들을 만들어줘야 해요.

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoStrings, swapTwoDoubles 함수를 살펴보면 함수 내용은 동일하지만 받아들이는 타입만 다른 걸 확인할 수 있습니다.

이는 단지 타입 때문에 동일한 함수를 여러 개 작성해야 한다는 걸 의미해요. (스위프트는 type-safe 언어로 String과 Double 등 서로 다른 타입의 값을 교환하는 걸 허용하지 않아요.)

 

그래서 사용하는 게 제네릭입니다.

swapTwoInts 함수를 제네릭 버전으로 바꾸면 아래와 같아요.

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

 

swapTwoInts 함수와 swapTwoValues 함수를 비교해 보면 함수명 뒤에 <T>가 붙고, 매개변수로 받는 타입이 T로 변경된 것 말고는 동일해요.

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

 

제네릭 버전 함수는 Int, String, Double과 같은 실제 타입 대신 placeholder 타입(함수 내 T)을 사용해요.

T라는 게 정확히 무엇인지 명시되어 있지는 않지만, T가 무엇이든 a와 b는 모두 T라는 동일한 타입을 사용해야 한다라고 추상적으로 명시하고 있어요.

T 대신 사용할 타입은 swapTwoValues 함수가 호출될 때마다 결정됩니다.

 

제네릭 함수와 비 제네릭 함수 사이의 또 다른 차이는 제네릭 함수는 함수 명 뒤에 꺽쇠 <>와 placeholder 타입 T가 붙는다는 거예요.

꺽쇠는 스위프트에게 T가 swapTwoValues 함수의 placeholder 타입이라고 말해줘요. 그래서 스위프트는 T라는 타입을 찾지 않고 함수가 호출될 때마다 결정되는 겁니다.

 

swapTwoValues 함수를 사용해 보면 아래처럼 함수 하나로 다양한 타입의 값들을 서로 교환할 수 있어요.

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt = 107, anotherInt = 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString = "world", anotherString = "hello"

 

그럼 placeholder 타입은 T 밖에 쓰지 못할까요?

그건 아닙니다. T는 for 문에서 흔히 쓰는 i, j, k처럼 이름을 정의할 필요가 없는 경우 쓰고, Dictionary<Key, Value> 나 Array<Element> 처럼 코드를 읽는 사람에게 placeholder 타입이 뭔지 정보를 줄 수 있어요.

하지만 굳이 이름을 부여할 필요가 없을 때는 위처럼 T, U, V 등 단일 문자를 사용하거나 Key, Value, Element처럼 Upper Camel Case로 사용합니다.

 

마지막으로, 제네릭 함수에 Dictionary의 Key처럼 타입에 제약을 주거나, 여러 개의 placeholder 타입을 지정할 수 있습니다.

func hashableFunction<T: Hashable>(someT: T) {
	//     
}
    
func differFunction<T, V>(t: T, v: V) {
	// 
}

 

728x90

'Swift' 카테고리의 다른 글

UIKit: UINavigationController  (0) 2023.09.09
UIKit: UIWindow  (0) 2023.09.09
Simulator와 실제 디바이스 차이 - 2  (0) 2023.08.28
Simulator와 실제 디바이스 차이 - 1  (0) 2023.08.28
UIKit: bounds, frame  (0) 2023.08.25