제네릭을 사용해 인자의 타입만 다르고 수행하는 기능이 동일한 것을
하나의 함수로 만들 수 있습니다.
따라서, 재사용 가능한 함수와 타입의 코드를 작성하는것을 가능하게 해줍니다.
이를위해 타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 합니다.
두 매개변수 a, b를 넣으면 a =b, b = a로 만드는 메서드를 작성해 보겠습니다.
func swapTwoValues<T> (_ a: inout T, _ b: inout T){
let tempA = a
a = b
b = tempA
print("\(a), \(b)")
}
플레이스 홀더안에 <T>는 구체적인 타입을 명시한게 아니라,
두 인자의 타입이 같다는것을 알려줍니다.
실행해 볼까요?
var a = 10
var b = 59
swapTwoValues( &a, &b ) // a: 56, b: 10
var c = "강아지"
var d = "고양이"
swapTwoValues( &c, &d ) // a = 고양이, b = 강아지
var a = 3.14
var b = 1.59
swapTwoValues( &a, &b ) // a = 1.59, b = 3.14
이렇듯 두 인자의 타입만 같다면 원하는 기능이 구현된걸 볼 수 있습니다.
타입 파라미터
플레이스 홀터 안에 타입 파라미터는 T말고도 원하는 이름 지을수 있습니다.
그리고 복수의 타입 파라미터를 콤마로 구분해 사용할수도 있죠.
파라미터 이름은 Key-value처럼 상관관계가 있는 경우는 이름을 파라미터 이름으로,
그렇지 않다면 T,U,V와 같은 단일 문자로 파라미터 이름을 짓습니다.
제네릭 타입
Swift에서는 제네릭 함수뿐만 아니라 타입을 정의할 수 있습니다.
스텍을 쌓고 pop 하는 경우를 예로 작성해보겠습니다.
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stack = Stack<Any>()
stack.push(123)
stack.push("가나다")
stack.push(3.141592)
print(stack.pop()) //3.141592
print(stack.pop()) //가나다
print(stack.pop()) //123
어렵지 않죠? 타입을 후술한다고 생각하면 됩니다.
확장
제네릭 타입도 익스텐션을 이용해 확장할 수 있습니다.
파라미터는 원래 선언한 파라미터 이름을 사용해야 합니다.
extension Stack{
var topItem: Element?{
return items.isEmpty ? nil : items[items.count - 1]
}
mutating func printTop(){
if let topItem = topItem{
print("스텍 최상단엔 \(topItem)이/가 있습니다.")
} else {
print("스텍은 비어있습니다.")
}
}
}
var stack = Stack<Any>()
stack.push("greensky")
stack.printTop() // 스텍 최상단엔 greensky이/가 있습니다.
stack.pop()
stack.printTop() //스텍은 비어있습니다.
타입 제한
제네릭 함수 선언시 파라미터 뒤에 타입뿐만 아니라, 상속받아야 하는 클래스나 프로토콜을
명시할 수 있습니다. 복수의 타입을 사용할 때 효과적입니다.
func gerericFunction<T: SomeType, U: SomeClass, V: SomProtocol>(_ a: T, _ b : U, _ c: V){
//구현부
}
예제
Strings 배열에서 원하는 문자열의 인덱스 찾기를 구현해보겠습니다.
func findIndex<T>(of toFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated(){
if value == nalueRofind {
return index
}
}
return nil
}
위 코드는 작동할까요?
작동하지 않습니다. value == toFind 부분에서 ==는 Equatble 프토콜을 따라야 사용할 수 있습니다.
그러므로 타입플레이스에 <T: Equatable>라 입력해줘야 ==를 사용할 수 있습니다.
func findIndex<T: Equatable>(of toFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated(){
if value == toFind {
return index
}
}
return nil
}
var findedIndex = findIndex(of: 3.12, in: [12.1, 11.4, 2.3])
// findedIndex = nil
findedIndex = findIndex(of: "greenSky", in: ["freeStyle", "greenSky", "blueSky"])
//findedIndex = 1
이렇게 작성해주어야 정상작동을 합니다.
연관 타입
연관타입은 프로토콜의 일부분으로 '타입'에 플레이스홀더 이름을 부여합니다.
즉, 특정 타입을 동적으로 지정해 사용할 수 있습니다.
예제
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
아래는 Item을 Element형으로 지정해 사용하는 경우입니다.
struct Stack<Element>: Container {
// 기존코드
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 프로토콜을 구현
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
기존에 있는 타입이 프로토콜에 선언된 사항이 모두 구현되 있다면,
특정 연관 타입을 추가할 수도 있습니다.
extinsion Array: Container {}
where 구문을 통해 타입에 조건을 걸어 제한을 둘 수 있습니다.
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
위 코드를 설명하면, 연관타입 Suffix는 SuffixableContainer를 따르고,
반드시 Container의 Item 타입이여야 한다는 조건을 건 것입니다.
이를 또 확장하면,
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
}
Stack의 연관 타입인 Suffix는 Stack이 됩니다.
Stack의 suffix메서드가 실행되며 또 다른 Stack을 반환하게 됩니다.
var stackOfInts = Stack<Int>()
stackOfInts.append(1)
stackOfInts.append(2)
stackOfInts.append(3)
let suffix = stackOfInts.suffix(2)
// suffix = [2, 3]
제네릭에서도 Where절을 사용할 수 있습니다
func someGeneric <C1: Container, C2: COntainer>(_ con1: C1, _ con2: C2) ->Bool
where C1.Item == C2.Item, C1.Item: Equatabel{
if con1.count != con2.count { return false }
for i in 0...<con2.count{
if con1[i] != con2[i] { return false }
}
return true
}
contaniner의 종류가 달라도 같은 인덱스의 모든 값이 같다면 true를 얻게 됩니다.
즉, Stack타입이든 배열타입이든 내용물만 같으면 true를 반환하게 됩니다.
where절을 포함하는 제네릭의 익스텐션
extension Stack where Element: Equatable{
func isTOp(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
Stack은 반드시 Equatable을 따라야 한다는 제한을 둔 프로토콜입니다.
이유는 위에 나왔다시피, Equatable프로토콜을 따르지 않으면 ==는 사용할 수 없으니까요.
제네릭 연관 타입에 where절 적용
protocol Container {
associatedtype Item
...
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
}
연관 타입 Iterator에 Iterator의 Element가 Item돠 같아야한다는 조건을 걸었습니다.
또한, 프로토콜을 상속하는 프로토콜에도 where절로 조건을 걸 수 있습니다!
protocol ComparbleContainer: Container where Item: Comparable { ... }
공부에 도움을 준 사이트들
https://jusung.gitbook.io/the-swift-language-guide/language-guide/22-generics
https://velog.io/@zooneon/Swift-제네릭에-대해-알아보자
'Language > Swift' 카테고리의 다른 글
Swift GCD(Grand Central Dispatch) [1] - 멀티스레딩 기초 (0) | 2021.03.22 |
---|---|
Swift 서브스크립트 (Subscripts) (0) | 2021.03.21 |
Swift 프로토콜 지향 프로그래밍 (POP) (0) | 2021.03.17 |
Swift 접근제어 (0) | 2021.03.16 |
Swift 고차함수 (0) | 2021.03.15 |