본문 바로가기
iOS📱/Swift

[ Swift ] 제네릭 ( Generic )

by 텅빈비니 2023. 1. 10.
반응형

안녕하세요 🐶
빈 지식 채우기의 비니🙋🏻‍♂️ 입니다.

오늘은 제네릭 ( Generic ) 대해 알아보는 시간을 가지겠습니다.

 

1.  개요

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

우리는!! 제네릭을 사용함으로써 코드 중복을 피하고, 유연하게 작성할 수 있다.

흔히 사용하는 Swfit 표준 라이브러리 대다수는 제네릭으로 선언되어 있습니다~

대표적으로 ArrayDictionary 가 있찌요!

 

정확한 제네릭에 대해서는 아래에서 설명을 드리겠습니다~

 

1-1. 제네릭 함수 ( Generic Function )

아아아주~ 간단하게 두 Int 값을 Print 출력하는 함수가 있고, 아래와 같이 구현해 보았습니다.

func printInts(_ a: Int, _ b: Int) {
    print("A값은 \(a), B값은 \(b)")
}

이렇게 하면 두 파라미터가 Int의 경우에는 문제없이 잘 돌아갑니다.

근데 만약 파라미터가 String, Double일 경우엔 어떨까요?! 네~ 물론 사용할 수 없습니다.

 

왜냐? Swift는 민감한 언어이므로 다른 타입이 돌아가기 위해서는 아래와 같이 따로 구현을 해야 합니다.

func printDoubles(_ a: Double, _ b: Double) {
    print("A값은 \(a), B값은 \(b)")
}

func printStrings(_ a: String, _ b: String) {
    print("A값은 \(a), B값은 \(b)")
}

뭐.. 이렇게 작성하면 사용할 수는 있습니다.

근데 타입이 추가가 될때마다 또는 형식이 바뀔때마다 오버로딩을 할 수도 있습니다..

너무 귀찬;;

func printTwoValues<T>(_ a: T, _ b: T) {	// (1)
    print("A값은 \(a), B값은 \(b)")
}
  1. T ( 제네릭 ) 으로 선언하여 타입에 제한을 두지 않게 사용한다.

여기서 TType Parameter 라고 부르는데, 

실제로 새로운 타입이 형성되는 것이 아니라,

실제 함수가 호출될 때 해당 매개변수의 타입으로 대체되는 Placeholder 입니다.

 

var someInt = 1
var aotherInt = 2
printTwoValues(someInt,  aotherInt)


var someString = "Hi"
var aotherString = "Bye"
printTwoValues(someString, aotherString)

위와 같이 제네릭 함수로 선언하여 파라미터 제약을 두지 않고 사용할 수 있습니다!!

 

다만, 여기서 a와 b를 각각의 다른 타입으로 선언하면 어떻게 될까? 에러가 남

printTwoValues(someInt, someString)

왜냐하면,

이미 첫 번째 파라미터 a의 T가 Int로 결정됐기 때문에,

두 번째 파라미터인 b 타입이 Int가 아니여서 에러가 나기 때문입니다.

 

똑같은 내용의 함수를 오버로딩 할 필요 없이 제네릭을 사용하면 된다!!

따라서 코드 중복을 피하고 유연하게 코드를 짤 수 있다!! 

 

1-2. 제네릭 타입 ( Generic Type )

구조체, 클래스, 열거형 타입에도 선언이 가능하다.
struct Stack<T> {
    let items: [T] = []
 
    mutating func push(_ item: T) { ... }
    mutating func pop() -> T { ... }
}

이렇게 T ( Generic ) Type 으로 선언할 수 있습니다.

그럼? 인스턴스는 어떻게 생성해야 하나요?

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

<> 를 통해 어떤 타입으로 사용할 것인지 명시해주어야 합니다.

응..? 근데 저거 어디서 많이 봤는데... 바로바로봐로~~~

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

넵! 개요에서 말한 배열 생성할 때랑 같습니다.

왜나하면 Array는 제네릭 타입으로 선언된 구조체이기 때문입니다~

 

이처럼 우리는 아무렇지 않게 제네릭을 사용해오고 있었습니다~ ㅎㅎ :)

 

2. 타입 제약 ( Type Constraints )

제네릭 함수 및 타입을 사용할 때, 특정 타입만 받을 수 있게 제약을 두는 것이다.

제네릭으로 선언된 함수 또는 타입에 사용이 가능합니다.

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

 

2-1. 프로토콜 제약

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

왜나하면 == 연산자는, a와 b 타입이 Equatable 프로토콜을 준수할 때만 사용이 가능합니다.

따라서 우리는 아래와 같이 구현을 해야 합니다!

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

이렇게 T: Equatable 이런 식으로 제약을 걸어 Equatable이란 프로토콜을 준수하는 파라미터만 받을 수 있습니다.

 

3. 제네릭 함수와 오버로딩

만약 특정 타입일 경우, 제네릭으로 선언된 함수 말고 다른 함수로 구현하고 싶으면?

이떄는?! 제네릭 함수를 오버로딩을 하면 됩니다.

func printTwoValues<T>(_ a: T, _ b: T) {
    print("A값은 \(a), B값은 \(b)")
}

func printTwoValues(_ a: Int, _ b: Int) {
    print("Int A값은 \(a), Int B값은 \(b)")
}

이렇게 할 경우,

타입이 명시된 함수가 제네릭 함수보다 우선순위가 높습니다!

var a = 1
var b = 2
printTwoValues(a, b)    // "Int A값은 1, Int B값은 2"


var c = "Hi"
var d = "Bean!"
printTwoValues(c, d)	// "A값은 Hi, B값은 Bean"

Int 타입으로 printTwoValues를 실행할 경우, 타입이 명시된 함수가 실행되고,

String 타입으로 printTwovalues를 실행하면, 제네릭 함수가 실행됩니다~~~!!

 

이상으로 제네릭 ( Generic ) 포스팅을 마치겠습니다.
틀린 부분이나 궁금한 사항은 댓글 남겨주세요~

 


참고

반응형