제네릭(Generics) 문법은 하나의 코드 블럭(함수, 클래스, 구조체 등)에서 다양한 타입의 데이터에 대응할 수 있도록 해주는 문법입니다.
즉, 한 번의 구현으로 모든 타입의 데이터에 대응할 수 있도록 하는 문법입니다.
✅ 제네릭(Generics) 문법을 사용하지 않은 함수
함수의 기능은 동일하지만, 데이터 타입 때문에 동일한 기능의 함수를 여러 개 만들어야 합니다.
동일한 기능의 함수가 여럿 존재하면 프로그램의 용량이 커질 뿐만 아니라 중복되는 코드 때문에 가독성이 떨어질 수 있다는 단점을 가지고 있습니다.
func swapInt(x: Int, y: Int) -> String{ let temp = x let x = y let y = temp return ("x=\(x), y=\(y)") } print(swapInt(x: 10, y: 20)) // x=20, y=10 func swapDouble(x: Double, y: Double) -> String{ let temp = x let x = y let y = temp return ("x=\(x), y=\(y)") } print(swapDouble(x: 1.1, y: 2.2)) // x=2.2, y=1.1 func swapString(x: String, y: String) -> String{ let temp = x let x = y let y = temp return ("x=\(x), y=\(y)") } print(swapString(x: "김", y: "철수")) //x=철수, y=김
✅ 제네릭(Generics) 문법을 사용한 함수
제네릭 문법을 사용하여 코드의 재사용성을 높이고, 코드의 중복성을 최소화할 수 있습니다. + 가독성 증가
func swapAny<T>(x: T, y: T) -> String{ let temp = x let x = y let y = temp return ("x=\(x), y=\(y)") } print(swapAny(x: 10, y: 20)) // x=20, y=10 print(swapAny(x: 1.1, y: 2.2)) // x=2.2, y=1.1 print(swapAny(x: "김", y: "철수")) //x=철수, y=김
제네릭 문법을 포함한 대표적인 예로는 컬렉션(Collection) 타입이 있습니다.
배열(딕셔너리, 세트)에는 어떠한 타입의 데이터가 할당될지 모르기 때문에 Swift를 설계할 때 컬렉션 타입을 제너릭 타입으로 설계했습니다.
//배열 var arr = Array<Int>() // <Int> //딕셔너리 var dic = Dictionary<Int, String>() // <Int, String> //세트 var st: Set<Int> = [] // <Int>
커스텀 타입 뒤에 타입 파라미터 <T>을 추가하면, 제네릭 타입으로 구현할 수 있습니다.
제네릭 타입으로 구현된 커스텀 타입은 속성, 메서드, 리턴형을 타입 파라미터 <T>로 대체 할 수 있습니다.
제네릭 타입을 확장할 때는 타입 파라미터 <T>를 작성하면 안 됩니다.
✅ 제네릭 구조체 생성
struct GenericsStruct<T>{ var member: T //멤버와이즈 생성자(Memberwise Initializer) } var A = GenericsStruct(member: 10) var B = GenericsStruct(member: "김철수") var C = GenericsStruct(member: 1.111)
✅ 제네릭 구조체의 확장(Extension)
extension GenericsStruct{ func extensionPrint(){ print("확장") } } extension GenericsStruct where T == Int{ // Int 타입의 맴버만 사용 가능한 함수 func sum() -> T{ return member + 1 } } A.sum() // 11
✅ 제네릭 클래스 생성
class GenericsClass<T>{ var member: T init(member: T){ self.member = member } } var A = GenericsClass(member: 10) var B = GenericsClass(member: "김철수") var C = GenericsClass(member: 1.111)
✅ 제네릭 클래스의 확장(Extension)
extension GenericsClass{ func extensionPrint(){ print("확장") } } extension GenericsClass where T == Int{ // Int 타입의 맴버만 사용 가능한 함수 func sum() -> T{ return member + 1 } } A.sum() // 11
✅ 제네릭 열거형 생성
열겨형의 경우에는 연관 값(Associated Value)을 가질 때만 제네릭으로 정의할 수 있습니다.
enum GenericsEnum<T>{ case member1 case member2(T) case member3(T) } var A = GenericsEnum.member2(10) var B = GenericsEnum.member2(10.11) var C = GenericsEnum.member3("짱구")
✅ 제네릭 열거형의 확장(Extension)
extension GenericsEnum{ func extensionPrint(){ print("확장") } }
제네릭에서는 특정 타입을 제약할 수 있습니다.
타입 파라미터 이름 뒤에 콜론을 사용하여 "프로토콜 제약" 또는 "클래스 제약"을 할 수 있습니다.
제네릭 함수에 들어가는 파라미터는 어떠한 타입인지 사전에 알 수 없습니다.
그 때문에 제네릭 타입으로 설계한 함수, 클래스 등에서는 사용의 제약이 존재합니다.
이러한 제약을 어느 정도 극복하기 위해 제네릭 설계 시 특정 프로토콜을 사용하여 해당 프로토콜을 준수하는 데이터 타입만 사용할 수 있도록 제약할 수 있습니다.
✅ 타입끼리 비교 <T: Equatable>
Equatable 프로토콜을 채택해야 타입끼리 비교(==, !=) 연산이 가능합니다.
스위프트의 기본 타입(Int, String, Double 등)은 Equatable 프로토콜을 준수하기 때문에 비교 연산 사용 가능합니다.
func comparison<T: Equatable>(x: T, y: T){ if x == y{ print("같다") } else if x != y{ print("다르다") } else{ print("???") } } comparison(x: 10, y: 20) // 다르다 comparison(x: "김", y: "김") // 같다 comparison(x: 10.11, y: 20.22) // 다르다
✅ 정수(Int) 타입끼리 연산 <T: BinaryInteger>
BinaryInteger 프로토콜을 채택하면 정수 타입끼리 연산할 수 있습니다.
스위프트에서 정수 타입은 BinaryInteger 프로토콜을 준수하지만 다른 타입(String, Double 등)은 준수하지 않기 때문에 연산할 수 없습니다.
func sum<T: BinaryInteger>(x: T, y: T) -> T{ return x + y } print(sum(x: 10, y: 20)) // 30 // print(sum(x: "김", y: "철수")) 사용 불가 // print(sum(x: 10.11, y: 20.22)) 사용 불가
타입 파라미터 <T> 이름 뒤에 특정 클래스 타입을 명시하면, 해당 클래스와 연관(상속 관계) 있는 클래스만 타입으로 사용할 수 있습니다.
구조체, 열거형은 상속 기능이 없으므로 사용할 수 없습니다.
✅ 제네릭(Generics)의 클래스 제약 구현
class 의무교육O{} class 의무교육X{} class 초등학교: 의무교육O{} //의무교육O 상속 class 중학교: 의무교육O{} //의무교육O 상속 class 대학교: 의무교육X{} let 초등학생 = 초등학교() let 중학생 = 중학교() let 대학생 = 대학교() func 의무교육<T: 의무교육O>(array: [T]){ // "의무교육O" 클래스만 사용 가능 print("의무교육 입니다.") } 의무교육(array: [초등학생]) // 의무교육 입니다. 의무교육(array: [중학생]) // 의무교육 입니다. 의무교육(array: [대학생]) // 에러: Type of expression is ambiguous without more context