Skip to content →

Generic Protocol

저는 처음에 Generic Protocol이란 말을 들었을 때 굉장히 헷갈렸습니다. Protocol만 해도 충분히 추상적인데, 왜 Generic이라는 다른 추상적 개념이 끌어들여 더더욱 추상적인 Generic Protocol이란 개념을 만들어야만 했는가.. 라는 생각까지도 했었죠. (어디서 많이 들어 본 소리네요 😁)

하지만 이름만 들었을 때는 이렇게 막연하고 헷갈리는 개념도, 차근차근 짚어 보면 그렇게 어렵지 않습니다.

해결하려는 문제

많은 타입들이 어떤 형태로든 “식별자(ID)”를 가지고 있습니다. 아래 코드 처럼요.

class Student {
    var ID: Int
}

이렇게 식별자를 가지고 있는 모든 클래스들을, Identifiable 이라는 프로토콜로 추상화 할 수 있을 것 같습니다.

protocol Identifiable {
    var ID: Int { get set }
}

class Student: Identifiable {
    var ID: Int 
}

그런데 어떤 종류 클래스들은 문자열 형태의 식별자를 채택하는 경우도 있습니다.

class AppleDevice {
    var ID: String = "a123-324adf-sdf" // 시리얼넘버들 같은 경우, 식별자에 문자열이 포함되기도 하죠
}

근데, 그럼 이제 AppleDeviceIdentifiable 을 충족시킬 수 없게 됩니다. Identifiable의 ID는 Int타입으로 정해져 있기 때문이죠.

그럼 이제 어떻게 해야 할까요? 지저분한 방법이지만, 아래와 같이 문제를 해결 할 수 있을 것 같습니다.

protocol IntIdentifiable {
    var ID: Int { get set }
}

protocol StringIdentifiable {
    var ID: String { get set }
}

class Student: IntIdentifiable {
    var ID: Int // 학번
}

class AppleDevice: StringIdentifiable {
    var ID: String // 시리얼번호
}

하지만 정말 지저분하네요. IntIdentifiable 이나 StringIdentifiable 같은 프로토콜을 만드는 방법 말고, 좀 더 깔끔한 방법은 없을까요? 있습니다! 그것은 다름 아닌… Generic Protocol 입니다!!(두둥!!)

How does it Solve?

‘ID’의 타입을 프로토콜에서 바로 지정해버리기 때문에 위와 같은 문제가 일어났습니다. 따라서 이 문제를 해결하려면, 프로토콜에서는 ‘ID’의 타입을 모르도록 해야합니다. 여기서 associatedtype 이라는 키워드가 들어옵니다.

protocol Identifiable {
    associatedtype T  
    var ID: T { get set }
}

associatedtye 은, Generic Type이나 Generic Function 을 만들 때 쓰던 “” 의 “<>”랑 비슷한 역할을 한다고 보면 됩니다. T 는 당연히 “”의 T가 되겠고요. 즉, T는 타입값인데, 프로토콜이 지정해주진 않고, 프로토콜을 채택하는 클래스 등에서 지정해주라는 표시입니다. 따라서 우리는 아래와 같이 쓸 수 있습니다.

protocol Identifiable {
    associatedtype ID_TYPE
    var ID: ID_TYPE { get set }
}

class Student: Identifiable {
    var ID: Int
    init(학번: Int) {
        self.ID = 학번
    }
}

let John = Student(학번: 301)

class AppleDevice: Identifiable {
    var ID: String
    init(serialNumber: String) {
        self.ID = serialNumber
    }
}

let iPhone = AppleDevice(serialNumber: "123-9xs90-dfx182")

즉, Generic Protocol은, Protocol에서 정의하는 프로퍼티나 메소드의 파라미터들의 타입을 Generic으로 받는 프로토콜이라고 설명 할 수 있을 것 같습니다.

Generic Protocol의 한계

굉장히 편리해보이는 Generic Protocol이지만, 조금만 쓰다보면 이런 저런 한계에 부딪히게 됩니다.
대표적으로, GenericProtocol은 Return할 수 없습니다. 그러려고 하면 아래와 같은 에러메시지를 만나게 됩니다.

Protocol ‘Identifiable’ can only be used as a generic constraint because it has Self or associated type requirements 

GenericProtocol은 Generic 문법의 Constraint으로서만 사용가능하다는 내용입니다. 즉, 다음과 같이만 사용가능하다는 얘기죠

func getSomeId<T:Identifiable>(from identifable:T) -> Int? {
    return identifiable.id as? Int
}

그리고 위 코드를 조금만 변형시켜보면, 왜 Swift가 Generic Protocol을 리턴타입으로 취급하지 않았는지 감을 잡을 수 있습니다.

func getSomeID<T:Identifiable>(from identifiable:T) -> ID_TYPE?? {
    return identifiable.id // 컴파일러는 ID_TYPE에 대해 모릅니다!!
}

컴파일러 입장에서는 ID_TYPE이 무엇인지 알 도리가 없습니다. 알 수 없는 타입을 리턴하려고 하니 당연히 컴파일 에러를 뱉겠지요. Identifiable을 그대로 리턴하는 함수도 마찬가지입니다. 리턴타입으로 취급하기에는 “뭔지 모를 타입을 속성으로 가진 녀석”이기 때문에, 타입을 아주 중요하게 생각하는 스위프트 입장에서는 용납하기 어려운 녀석이 되는 것이지요.

하지만 이런 내용도 이제 과거의 유산이 되었습니다. Swift5.1에서는 Opaque Return Type이라는 개념이 들어와서, 이런 Generic Protocol도 리턴타입으로 사용 할 수 있게 됩니다.

다음 글에서는 Opaque Return Type에 대해 정리 해 보겠습니다.

Published in Basics 프로그래밍

Comments

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

%d 블로거가 이것을 좋아합니다: