Old_SWIFT(221012)/기본이야기

제네릭(Generic)이란?

KataRN 2022. 9. 28. 10:57
반응형

안녕하세요.

 

KataRN입니다.

 

오늘은 제네릭(Generic)에 대해서 알아보겠습니다.

 

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

Swift 표준 라이브러리의 대다수는 제네릭으로 선언되어 있다고 합니다.(Array, Dictionary도 제네릭타입입니다.)

 

우선 제네릭이 뭔지에 대해 설명드리기 위한 예제를 만들었습니다.

func showParam(_ a: Int, _ b: Int) {
    print(a, b)
}

파라미터가 뭔지 프린트하는 단순한 함수입니다.

보시다싶이 Int인 경우에만 작동합니다.

아래처럼 Double형태인 5.5를 넣었으니 안된다고 하겠죠.

 

제네릭을 사용하지 않았던 이전의 "나"였으면 아마 함수를 추가했을 것입니다.

func showParam(_ a: Int, _ b: Int) {
    print(a, b)
}

func showParam(_ a: Double, _ b: Double) {
    print(a, b)
}

이렇게 말이죠.

 

이것은 오버로드라고 합니다. 오버라이드하고 다릅니다.

오버로드는 같은 함수인데 타입형에 따라 함수가 구분되는 것을 말합니다.

문제는 String으로 넣으면 또 똑같은 일이 일어나고 해결하기 위해서는 String형으로 들어오는 함수를 만들어야겠죠?

 

그럼 하나의 함수가 9줄 이상이 되겠네요.

제네릭을 알고있는 지금의 저라면 이렇게 함수를 만들겠습니다.

func showParam<T>(_ a: T, _ b: T) {
    print(a, b)
}

 

이렇게 3줄로 끝이납니다...

다시말해 타입에 제한받지 않는 코드를 만들때 사용합니다.

<>꺽쇠를 이용하여 사용하며 일반적으로 Type Parameter를 뜻하는 T를 사용한다고합니다.

(다른것으로 사용해도 되지만 T V 같은 단일 문자, 혹은 Upper Camel Case를 사용한다고 합니다.)

 

제네릭을 이용한 함수는 제네릭함수라고 합니다.

좀더 나아가서 구조체, 클래스, 열거형 타입에도 선언할 수 있고 제네릭 타입이라고 합니다.

 

그리고 특정 클래스의 하위클래스나 특정 프로토콜을 준수하는 타입만 받을 수 있게 제약을 둘 수 있습니다.

우선 프로토콜 제약을 보겠습니다.

다시 예제를 한번 보시죠.

 

문제가 없을 줄 알았는데 문제가 생겼습니다.

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

Equatable 프로토콜을 준수 안하는 경우가 올 수 있으니까 못하게 하는것입니다.

 

그렇기 때문에 제약을 줘보겠습니다.

 

이러면 문제가 없어집니다.

너무 신기해...

 

이번엔 클래스 제약을 보겠습니다.

아래 보시면 클래스들을 정의했고 함수를 정의했습니다.

printName 라는 함수는 Human으로 제약을 추가하였습니다.

class Animal { }
class Human { }
class KataRN: Human { }
 
func printName<T: Human>(_ a: T) { }

이렇게 하여 함수를 사용해보겠습니다.

animal은 Human이 아니며 상속도 받지 않아서 이처럼 사용이 불가능합니다.

 

다음은 제네릭 확장에 대해 알아보겠습니다.

 

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

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

구현부에서 타입파라미터가 Element이기 때문에 Element로 사용해야됩니다.

 

확장에서 제네릭선언이나 다른타입파라미터를 사용하면 아래처럼 안됩니다.

 

이럴 경우에는 where을 통해 확장 또는 제약을 주면 됩니다.

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

 

이럴 경우에는 Array<Int>에서는 pop()을 사용할 수 있지만 Array<String>는 사용할 수 없습니다.

 

저는 최근 공모전에 제출한 앱에서 제네릭함수를 사용하지 않고 오버로드만 하였는데

제네릭함수 설정 + 특정타입지정 함수를 오버로드를 하게 되면 어떻게 될까요?

우선순위 : 타입지정된 함수 > 제네릭함수 이기 때문에 타입지정된 함수가 우선실행된다고합니다.

 

제네릭을 공부하다보니 associatedtype 가 계속 언급됩니다.

- 프로토콜의 일부분으로 타입에 플레이스홀더 이름을 부여합니다. 다시말해 특정 타입을 동적으로 지정해 사용할 수 있습니다.

아래 처럼 지정하면 Item은 어떤 타입도 될 수 있다고합니다.

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

 

associatedtype는 좀 헷갈려서 조만간 다시 정리해보도록할게요 ㅠㅠ

 

오늘도 긴글 읽어주셔서 감사합니다.

반응형