제네릭(Generics)

썹스·2022년 12월 4일
0

Swift 문법

목록 보기
45/68

제네릭(Generics)

제네릭(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=김

🙂 사실 Swift 언어에는 제네릭 문법이 많이 포함되어 있습니다.

제네릭 문법을 포함한 대표적인 예로는 컬렉션(Collection) 타입이 있습니다.

배열(딕셔너리, 세트)에는 어떠한 타입의 데이터가 할당될지 모르기 때문에 Swift를 설계할 때 컬렉션 타입을 제너릭 타입으로 설계했습니다.

//배열
var arr = Array<Int>()    // <Int>

//딕셔너리
var dic = Dictionary<Int, String>()    // <Int, String>

//세트
var st: Set<Int> = []    // <Int>

커스텀 타입(Custom Type)의 제네릭(Generics)

  • 커스텀 타입 뒤에 타입 파라미터 <T>을 추가하면, 제네릭 타입으로 구현할 수 있습니다.

  • 제네릭 타입으로 구현된 커스텀 타입은 속성, 메서드, 리턴형을 타입 파라미터 <T>로 대체 할 수 있습니다.

  • 제네릭 타입을 확장할 때는 타입 파라미터 <T>를 작성하면 안 됩니다.

📌 제네릭(Generics) 구조체(struct)

✅ 제네릭 구조체 생성

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

📌 제네릭(Generics) 클래스(class)

✅ 제네릭 클래스 생성

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

📌 제네릭(Generics) 열거형(enum)

✅ 제네릭 열거형 생성

열겨형의 경우에는 연관 값(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("확장")
    }
}

제네릭(Generics)의 타입 제약(Type Constraint)

제네릭에서는 특정 타입을 제약할 수 있습니다.

타입 파라미터 이름 뒤에 콜론을 사용하여 "프로토콜 제약" 또는 "클래스 제약"을 할 수 있습니다.

📌 제네릭(Generics)의 프로토콜 제약

제네릭 함수에 들어가는 파라미터는 어떠한 타입인지 사전에 알 수 없습니다.

그 때문에 제네릭 타입으로 설계한 함수, 클래스 등에서는 사용의 제약이 존재합니다.

이러한 제약을 어느 정도 극복하기 위해 제네릭 설계 시 특정 프로토콜을 사용하여 해당 프로토콜을 준수하는 데이터 타입만 사용할 수 있도록 제약할 수 있습니다.

✅ 타입끼리 비교 <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))   사용 불가

📌 제네릭(Generics)의 클래스 제약

타입 파라미터 <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
profile
응애 나 코린이(비트코인X 코딩O)

0개의 댓글