2014년 6월 17일 화요일

Swift - 제너릭(Generics)

제너릭(Generic, 본래 발음은 ‘저너릭'에 가깝지만…)은 쉽게 설명하기 어려운 용어 같다. C++의 STL등의 템플릿을 체험해 봤다면 특정 부타입을 명시해서 내부에서 쓸 데이터의 타입을 정의하는 것을 종종 볼 수 있는데 이 제너릭은 그런 방식을 정의하기 위해 사용하는 것과 비슷하다.

이번에는 이 제너릭에 대해 얉게 파고들어 보자. (깊게 안들어간다 -_-)

제너릭의 실제 사용예는 배열(Array)이나 사전(Dictionary)형 정의 시 볼 수 있다.
// Int형의 10개짜리 배열 생성
var array = Array<Int>(count: 10, repeatedValue: 0)

// String타입의 키와 Int 타입의 데이터를 가질 수 있는 사전형 생성
var dict = Dictionary<String, Int>()
이런 식으로 컨테이너 타입에 넣기 위한 아이템의 타입을 정의할 수 있는데 여기서 보이는 꺽쇠(<, >)로 정의하는 부분을 구현하려면 이 제너릭을 사용해야 한다.

제너릭 구현

일반 함수에도 제너릭을 붙일 수 있지만 요즘처럼 OOP시대에서 함수를 쓰기보다는 클래스나 구조체로 설계하는 것이 일반적이니 구조체 형식으로 보는게 좋을 것 같다. 아래 예제는 (공식 문서에는 스택을 구현하길래 달라보이려고) 자료구조 중 큐(Queue)를 구현하는 예제다.
struct MyQueue<T> {
    // T 타입의 배열(Array) 생성
    var items = T[]()
   
    mutating func push(item: T) {
        self.items.append(item)
    }
   
    mutating func pop() -> T? {
        if self.items.count <= 0 {
            return nil
        }
       
        let item = self.items[0]
        self.items.removeAtIndex(0)
        return item
    }
}
구조체 선언에서 이름 옆에 <T>를 명시하는 것이 보인다. 이것이 제너릭이다. 여기서 쓰인 T라는 이름은 아무거나 막 쓸 수 있는데 아무래도 T로 쓰는게 간단하고 알아보기에도 나쁘지 않은것 같다.

실제로 테스트 해 보면 잘 동작한다.
var q = MyQueue<Int>()
q.push(10)
q.push(20)
q.pop()     // 10
q.pop()     // 20
q.pop()     // nil
원하는대로 동작함을 알 수 있다.

비교가능화? Equatable

위 예제에 메소드를 하나 추가했다. 푸쉬한 데이터 중 첫번째와 두번째가 동일한지를 확인하기 위한 메소드다. 생뚱맞은 예만 자꾸 나오는것 같아서 참 내가 한심하다. :-)
struct MyQueue<T> {
    // T 타입의 배열(Array) 생성
    var items = T[]()
    
    ... 생략 ...
    
    // 추가한 메소드
    func isEqualFirstAndSecond() -> Bool {
        if self.items.count < 2 {
            return false
        }
       
        let first = self.items[0]
        let second = self.items[1]
       
        // 아래에서 '==' 오퍼레이터가 없다고 에러가 발생
        return first == second
    }
}
실제로 에러 내용은 could not found an overload for ‘==‘ that accepts the supplied arguments 와 같은 식이다. 당연히 여기서는 제너릭으로 정해진 타입이 아닌 알 수 없는 타입을 사용하기 때문에 비교연산자가 오버로드 되어있는지 조차 알 수가 없는 것이다.

이 경우 사용 할 수 있는 키워드가 equatable 이다. ‘동일시 할 수 있는’ 이라는 의미겠지만 개인적으로 ‘비교가능화’ 라고 표현한다.
struct MyQueue<T: Equatable> {

    ... 생략 ...
}
제너릭에 Equatable 이라고 정의를 하면 이런 에러는 사라진다. 실제로 실행시켜보면 Int 타입에 한해서는 잘 동작한다고 할 수 있다.
var q = MyQueue<Int>()
q.push(10)
q.push(20)
q.isEqualFirstAndSecond()   // false

q.pop()
q.pop()
q.push(1)
q.push(1)
q.isEqualFirstAndSecond()   // true
물론 Equatable 이 제대로 동작하기 위해서는 ‘==' 오퍼레이터가 미리 오버로드되어 있어야 가능하다. 오퍼레이터 오버로드를 참고하자.

공식 레퍼런스 문서에 나오는 내용을 보면 이 외에도 관련내용이 더 있다. 예를 들어 제너릭에 넣을 수 있는 타입을 제한(Type Constraints) 라던가 연관 타입(Associated Types)이라던가 많은 내용이 있지만 중요한 것은 위의 내용이 전부인 것 같아 생략한다. 필요하다면 직접 찾아보자. -_-;

개인적으로 제너릭은 자주 쓸 만한 개념이 아닌 것 같다. 대부분의 경우 배열형(Array)과 사전형(Dictionary)을 이용하면 해결이 된다. 아무래도 수학이나 데이터가공 분야가 아니면 잘 쓰이진 않을 것 같다.

[관련글] Swift - 구조체(Structure) 훑어보기
[관련글] Swift - 클래스(Class) 훑어보기
[관련글] Swift - 오퍼레이터 오버로드(Operator Overloads)

[돌아가기] 스위프트(Swift) 가이드

댓글 없음 :