[Swift] 제네릭에 대해 알아보자!

zooneon·2020년 11월 27일
1

Swift 기본 문법

목록 보기
14/14

본 내용은 '스위프트 프로그래밍' 책을 학습한 후 이를 바탕으로 작성한 글입니다.

제네릭

  • 제네릭을 이용해 코드를 구현하면 어떤 타입에도 유연하게 대응할 수 있다.
  • 제네릭으로 구현한 기능과 타입은 재사용하기도 쉽고, 코드의 중복을 줄일 수 있다.
  • 제네릭을 사용하고자 할 때는 제네릭이 필요한 타입 또는 메서드의 이름 뒤의 홀화살괄호 기호 사이에 제네릭을 위한 타입 매개변수를 써주어 제네릭을 사용할 것임을 표시한다.
타입 또는 메서드 이름 <타입 매개변수>

제네릭 함수

  • 제네릭 함수는 실제 타입 이름을 써주는 대신에 placeholder를 사용한다. [ eg: T, V, U ]
  • placeholder는 타입의 종류를 알려주지 않지만 어떤 타입이라는 것은 알려준다.
  • placeholder의 실제 타입은 함수가 호출되는 순간 결정된다.
  • placeholder는 타입 매개변수로 쓰일 수도 있는데, 이 타입 매개변수는 함수를 호출할 때마다 실제 타입으로 치환된다.
  • 하나의 타입 매개변수를 갖지 않고 여러 개의 타입 매개변수를 갖고 싶다면 홀화살괄호 기호 안쪽에 쉼표로 분리한 여러 개의 타입 매개변수를 지정해줄 수 있다. [ eg: <T, U> ]
  • 의미 있는 이름으로 타입 매개변수의 이름을 지정해주면 제네릭 타입 및 제네릭 함수와 타입 매개변수와의 관계를 더 명확하게 표현해줄 수 있다. [ eg: Dictionary<Key, Value>, Array[Element], .. ]

제네릭 타입

  • 제네릭 타입을 구현하면 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있다.
  • 제네릭 타입을 정해주면 그 타입에만 동작하도록 제한할 수 있어 안전하고 의도한 대로 기능을 사용하도록 유도할 수 있다.
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

var intStack: Stack<Int> = Stack<Int>()
var doubleStack: Stack<Double> = Stack<Double>()

intStack.push(1)
intStack.push(2)
intStack.push(3)

print(intStack)     //Stack<Int>(items: [1, 2, 3])

doubleStack.push(1.0)
doubleStack.push(2.0)
doubleStack.push(3.0)

print(doubleStack)      //Stack<Double>(items: [1.0, 2.0, 3.0])

타입 제약

  • 타입 제약은 타입 매개변수가 가져야 할 제약사항을 지정할 수 있는 방법이다. [ eg: 클래스 상속을 받는 경우, 특정 프로토콜을 준수해야 하는 경우 ]
  • 타입 제약은 클래스 타입 또는 프로토콜로만 줄 수 있다.
  • 제네릭 타입에 제약을 주고 싶으면 타입 매개변수 뒤에 콜론을 붙인 후 원하는 클래스 타입 또는 프로토콜을 명시하면 된다.
  • 여러 제약을 추가하고 싶다면 콤마로 구분하는 것이 아닌 where절을 이용한다.
func swapTwoValues<T: BinaryInteger>(_ a: inout T, _ b: inout T) {
    // 함수 구현
}

//제네릭 타입 제약 추가
func swapTwoValuesPlusConstraint<T: BinaryInteger>(_ a: inout T, _ b: inout T)
    where T: FloatingPoint {
    //함수 구현
}

프로토콜의 연관 타입

  • 프로토콜을 정의할 때 연관 타입을 함께 정의할 수 있다.
  • 연관 타입은 타입 매개변수의 역할을 프로토콜에서 수행할 수 있도록 만들어진 기능이다.
protocol Container {
    associatedtype ItemType
    var count: Int { get }
    mutating func append(_ item: ItemType)
    subscript(i: Int) -> ItemType { get }
}

class MyContainer: Container {
    var items: Array<Int> = Array<Int>()
    
    var count: Int {
        return items.count
    }
    
    func append(_ item: Int) {
        items.append(item)
    }
    
    subscript(i: Int) -> Int {
        return items[i]
    }
}

제네릭 서브스크립트

  • 서브스크립트도 제네릭을 활용하여 타입에 제한 없이 유연하게 구현할 수 있다.
  • 타입 제약을 사용하여 제네릭을 활용하는 타입에 제약을 줄 수도 있다.
protocol Container {
    associatedtype ItemType
    var count: Int { get }
    mutating func append(_ item: ItemType)
    subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
    
    mutating func append(_ item: Element) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> Element {
        return items[i]
    }
}

extension Stack {
    subscript<Indices: Sequence>(indices: Indices) -> [Element]
    where Indices.Iterator.Element == Int {
        var result = [ItemType]()
        
        for index in indices {
            result.append(self[index])
        }
        return result
    }
}

var intStack: Stack<Int> = Stack<Int>()
intStack.append(1)
intStack.append(2)
intStack.append(3)
intStack.append(4)
intStack.append(5)

print(intStack[0...2])    //[1, 2, 3]
profile
블로그 이전했습니다. https://blog.zooneon.dev

0개의 댓글