본문 바로가기
Swift/기본

제네릭 (제네릭 제약, 제네릭 확장, 제네릭 함수와 오버로딩)

by 밤새는탐험가 2024. 4. 16.

타입 제약

제네릭 함수와 타입을 사용할 때, 특정 클래스의 하위 클래스나

특정 프로토콜을 준수하는 타입만 받을 수 있게 제약할 수 있다.

 

 

프로토콜 제약

 

두 개의 값을 파라미터로 받을 때, 값이 같으면 true 또는 값이 다르면 false를 반환하는 

함수를 제네릭으로 선언한다면?

 

func isSameValues<T>(_ a: T, _ b: T) -> Bool {
    return a == b               // Binary operator '==' cannot be applied to two 'T' operands
}

 

에러가 발생한다. 

이유는, " == " 이라는 연산자는 a와 b의 타입이 Equatable 이라는 프로토콜을 준수할 경우에만 사용할 수 있기 때문이다. 

 

따라서 아래와 같이 타입 파라미터가 " Equatable " 프로토콜을 준수한다고 작성해야 한다.

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

 

 

 

클래스 제약

프로토콜 제약과 동일하지만, 해당 자리에 프로토콜이 오는 것이 아닌, 클래스 이름이 온다.

class Bird { }
class Human { }
class Teacher: Human { }
 
func printName<T: Human>(_ a: T) { }

 

각 클래스의 인스턴스를 생성하고 난 후에 

printName 함수를 통해 호출한다. 

 

let bird = Bird.init()
let human = Human.init()
let teacher = Teacher.init()
 
printName(bird)                  // Global function 'printName' requires that 'Bird' inherit from 'Human'
printName(human)
printName(teacher)

 

human 인스턴스와 Human 클래스를 상속 받은 teacher 인스턴스는 

printName 이란 제네릭 함수를 실행할 수 있지만, 

Human 클래스를 상속받지 않은 bird 인스턴스는 실행할 수 없다.

 

 

 

제네릭 확장

제네릭 타입인 Array를 확장하고 싶다면?

extension Array {
    mutating func pop() -> Element {
        return self.removeLast()
    }
}

 

제네릭 타입을 확장하면서 타입 파라미터를 사용할 경우, 

실제 Array 구현부에서 타입 파라미터가 Element 이기 때문에 Element를 써야 한다. 

 

즉, 새로운 제네릭을 선언하거나 다른 타입 파라미터를 사용하면 오류 발생한다.

 

 

또는 where을 통해 확장 또는 제약을 걸 수 있다.

아래와 같이 Element가 FixedWidthInteger라는 프로토콜을 준수해야 한다라고 제약을 준다고 하면

extension Array where Element: FixedWidthInteger {
    mutating func pop() -> Element { return self.removeLast() }
}

 

 

FixedWidthInteger 프로토콜을 준수하는 Array<Int> 형인 nums는 

extension에서 구현된 pop 이라는 메서드를 사용할 수 있지만, 

 

Array<String> 형인 strs는 pop 이라는 메서드를 사용할 수 없다.

 

let nums = [1, 2, 3]
let strs = ["a", "b", "c"]
 
nums.pop()              // O
strs.pop()              // X

 

 

 

제네릭 함수와 오버로딩

제네릭은 타입에 관계없이 동일하게 실행되지만,

특정 타입일 경우, 제네릭 말고 다른 함수로 구현하고 싶다면

제네릭 함수를 오버로딩하면 된다.

 

func swapValues<T>(_ a: inout T, _ b: inout T) {
    print("generic func")
    let tempA = a
    a = b
    b = tempA
}
 
func swapValues(_ a: inout Int, _ b: inout Int) {
    print("specialized func")
    let tempA = a
    a = b
    b = tempA
}

 

 

swapValues를 하나는 제네릭 타입을 설정하고, 

다른 하나는 타입을 지정해서 구현했다. 

 

이 경우에는 타입 지정된 함수가 제네릭 함수보다 우선순위가 높기 때문에 

Int 타입으로 함수를 호출하면, 타입 지정된 함수가 실행되고, 

String 타입으로 함수를 호출하면, 제네릭 함수가 실행된다.

 

var a = 1
var b = 2
swapValues(&a, &b)          //"specialized func"
 
 
var c = "Hi"
var d = "Sodeul!"
swapValues(&c, &d)          //"generic func"

 

 

[Swift] inout 매개 변수, 앰퍼샌드(&) (tistory.com)

 

[Swift] inout 매개 변수, 앰퍼샌드(&)

In-Out Parameters copy-in copy-out 함수 매개 변수는 기본적으로 상수(Constant)입니다. 함수 매개 변수의 값을 해당 함수의 본문 내에서 변경하려고 하면 compile-time 오류가 발생합니다. 함수에서 매개 변수

baechukim.tistory.com

 

 

 

'Swift > 기본' 카테고리의 다른 글

접근 제어자 (Access Control)  (0) 2024.04.17
서브스크립트  (1) 2024.04.16
제네릭  (0) 2024.04.15
고차함수  (0) 2024.04.15
오류처리  (0) 2024.04.13