[Swift] 제네릭

김태형·2023년 4월 19일
0

Swift

목록 보기
21/22

오늘은 스위프트의 문법 중 매우 중요한 요소 중 하나인 제네릭에 대해 알아보았다.
사실 아직 제대로 사용해본 적은 많이 없지만, 이번 기회를 통해 많이 사용해봐야겠다.


제네릭

  • <>로 정의
  • 어떤 타입에도 유연하게 대응할 수 있음
  • 제네릭으로 구현한 기능과 타입은 재사용하기도 쉽고, 코드의 중복을 줄일 수 있음
    • 깔끔하고 추상적인 표현 가능
제네릭을 사용하고자 하는 타입 이름 <타입 매개변수> (함수의 매개변수 ...)

  • 제네릭 함수는 실제 타입 이름을 써주는 대신에 플레이스홀더(주로 T)를 사용
    • 어떤 타입이 들어올 지 모르니깐 (호출되기 전까지) T(Type)라고 정의해두는 것
  • 플레이스홀더는 타입의 종류는 알려주지 않지만, 어떤 타입이라는 것은 알려줌
    • 실제 타입은 함수가 호출되는 순간에 결정됨
      • 만약 Int가 전달되었으면 T는 Int가 되고, String이 전달되었으면 T는 String이 됨
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {    //둘 다 T로 정의했기 때문에, 타입이 같음
    let tmpA:T = a
    a = b
    b = tmpA
}

print("\(numberOne), \(numberTwo)")    // Ok
print("\(stringOne), \(stringOne)")    // Ok 

//string이나 Int가 둘 다 가능함

print("\(stringOne), \(numberOne)")    // 에러 -> 둘의 타입이 다르기 때문에

제네릭 타입

  • 제네릭 타입을 구현하면 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있음
    • Array나 Dictionary 타입이 자신의 요소로 모든 타입을 대상으로 동작할 수 있는 것과 유사
//제네릭을 사용하지 않은 IntStack

struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

//제네릭을 사용한 Stack

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
  • 익스텐션을 통해 제네릭을 사용하는 타입에 기능을 추가하고자 한다면 익스텐션 정의에 타입 매개변수를 정의하지 않아야 함
  • 하지만 원래 명시한 타입 매개변수를 익스텐션에서 사용 가능
extension Stack {    //원래 정의는 Stack<Element>로 되어있지만, 정의하지 않음. 하지만 기존에 정의한 Element 사용 가능
    var topElement: Element? {
        return self.items.last
    }
}

타입 제약

  • 제네릭 타입에 제약을 줄 수 있음
  • 클래스 타입 또는 프로토콜로만 줄 수 있음 —> 열거형, 구조체 등의 타입은 타입 제약의 타입으로 사용할 수 없음
  • 타입 매개변수 뒤에 원하는 클래스 타입 또는 프로토콜을 명시하면 됨
  • ex. 빼기를 구현하여 사용할 수 있도록 하려면 빼기 연산자를 사용할 수 있는 타입이어야 함
struct Stack<Element: Hashable> {

}

//BinaryInteger 프로토콜을 준수하는 타입으로 연산을 제한함
func substractTwoValue<T: BinaryInterger>(_ a: T, _ b: T) -> T {
	return a - b
}

프로토콜 연관 타입

  • 프로토콜에서 사용할 수 있는 플레이스홀더 이름
  • ‘종류는 알 수 없지만, 어떤 타입이 여기에 쓰일 것이다’
protocol Container {
    associatedtype ItemType
    var count: Int { get }
    mutating func append(_ item: ItemType)
    subscript(i: Int) -> ItemType { get }
}

//Container에서는 타입에 대해 지정해주지 않았는데, MyContainer에서는 지정을 해줌
//연관 타입 ItemType 대신에 실제 타입인 Int 타입으로 구현
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]
    }
}

제네릭 서브스크립트

  • 서브스크립트도 제네릭을 활용하여 타입에 제한 없이 구현 가능

0개의 댓글