티스토리 뷰

Swift/야곰의 스위프트

22장 제네릭

할루루 2018. 4. 27. 20:22

[시작]


- 타입에 유용하게 대응하기 위한 기능

- 재사용하기 쉽고 코드의 중복을 줄일 수 있음

- 깔끔하고 추상적인 표현이 가능함



네릭을용하고자 름<타입 개변수>

네릭을용하고자 름<개변수>(함수 개변수...)



- 제네릭을 사용하지 않고 Any 타입으로 함수를 만들었을 때 발생할 수 있는 문제

 : Swift는 강타입 언어이므로 Any 타입으로 함수를 만들었으면 Any 타입만 함수에 들어갈 수 있다.

   매개변수를 Any타입으로 바꾸어주어야 하는 과정이 불필요하게 추가되는 것이다.



[제네릭 함수]



func swapValues<T>(_ first: inout T, _ second: inout T) {

    let temp: T = first

    first = second

    second = temp

}


var a: Int = 3

var b: Int = 4

swapValues(&a, &b)

print(a, b)   //4 3 


var c: String = "First"

var d: String = "Second"

swapValues(&c, &d)

print(c, d)   //Second First


- 제네릭 함수는 실제 타입 이름(Int 등) 대신 placeholder를 사용, 위에서는 T로 사용

 : placeholder는 타입의 종류를 알려주지 않지만 어떤 타입이라는 것은 알려줌

 : placeholder의 실제 타입은 함수 호출 시 결정됨


- placeholder 타입 T는 타입 매개변수의 한 예로 들 수 있다.

 : placeholder 타입의 이름을 지정하고 명시, 함수의 이름 뒤 <> 안쪽에 위치

 : 함수의 매개변수의 타입으로, 함수의 반환 타입으로 사용 가능

 : 함수 내부 변수의 타입 지정을 위해 사용 가능

 : 여러 개의 타입 매개변수를 지정할 수 있음 ex) <T, U, V>

 : 의미 있는 이름으로 타입 매개변수의 이름을 지정하여 제네릭 타입 및 제네릭 함수와 타입 매개변수와의 관계를 명확히 표현해 주자.

 : 대문자 카멜 케이스 사용



[제네릭 타입]


- 제네릭 타입을 구현하여 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작하게 할 수 있음


struct Stack<Element> {

    var items: [Element] = []

    mutating func push(_ item: Element) {

        items.append(item)

    }

    mutating func pop() -> Element {

        return items.removeLast()

    }

}


var doubleStack = Stack<Double>()

doubleStack.push(2.5)

doubleStack.push("asdf")    //오류


var anyStack = Stack<Any>()

anyStack.push("fdasf")

anyStack.push(124)


위에서 제네릭이 아니라 Any 타입으로 구조체의 프로퍼티와 메서드를 정의했다면 한 가지 타입만 들어갈 수 있는 스택을 보장하지 못한다.
제네릭을 사용하여 함수 호출 시 결정된 타입에만 동작하도록 제한할 수 있고 이는 개발자가 의도한 대로 기능을 사용하게 유도할 수 있다.




[제네릭 타입 확장]


- 익스텐션을 통해 제네릭을 사용하는 타입에 기능을 추가하고자 한다면 익스텐션 정의에 타입 매개변수를 명시하지 않아야 한다.

- 대신 원래의 제네릭 정의에 명시한 타입 매개변수를 익스텐션에서 사용할 수 있다.


struct Stack<Element> {

    var items: [Element] = []

    mutating func push(_ item: Element) {

        items.append(item)

    }

    mutating func pop() -> Element {

        return items.removeLast()

    }

}


extension Stack {

    var peek: Element? {

        return items.last

    }

}


위에서 정의한 제네릭 타입 구조체에 익스텐션을 사용하여 프로퍼티를 추가하였다.

이 때 익스텐션에서는 타입 매개변수를 명시하지 않고, 원래의 제네릭 정의에 명시한 타입 매개변수를 익스텐션에서 사용한다.



[타입 제약]


- 제네릭 함수가 처리해야 할 기능이 특정 타입에 한정되어야만 처리할 수 있다던가, 제네릭 타입을 특정 프로토콜을 준수하는 타입만 사용할 수 있도록 제약을 두어야 할 때가 있음

 : 타입 매개변수가 특정 클래스를 상속받거나 특정 프로토콜을 준수하는 타입일 수 있음

- 타입 제약은 클래스 타입 또는 프로토콜로만 줄 수 있다. 열거형, 구조체 등의 타입은 타입 제약의 타입으로 사용할 수 없다.

- 타입 매개변수 뒤에 콜론을 붙인 후 원하는 클래스 타입 또는 프로토콜을 명시함으로서 구현.

- 여러 제약을 추가하기 위해 where절을 사용할 수 있음


func subtractTwoValue<T>(_ val1: inout T, _ val2: inout T) -> T{

    return val1 - val2

    //Binary operator '-' cannot be applied to two 'T' operands

}


위의 코드는 오류를 내는데, 뺄셈을 위해서는 뺄셈 연산자를 사용할 수 있는 타입이 정의되어야 하기 때문이다.

BinaryInteger 프로토콜을 준수하는 타입으로 한정해두어 해결할 수 있다.


func subtractTwoValue<T: BinaryInteger>(_ val1: inout T, _ val2: inout T) -> T{

    return val1 - val2

}


타입 제약에 자주 사용할 만한 프로토콜 : 

Hashable | Equatable | Comparable | Indexable | IteratorProtocol | Error | Collection | CustomStringConvertible


- 타입 매개변수마다 제약 조건을 달리해서 구현할 수 있다.



[프로토콜의 연관 타입]


- 연관 타입 : 프로토콜에서 사용할 수 있는 placeholder 이름

- 제네릭의 타입 매개변수의 역할을 프로토콜에서 실행할 수 있도록 하는 기능

- '내부에서 사용할 타입이 그 어떤 것이어도 상관 없지만, 하나의 타입임은 분명하다'


protocol Container {

    associatedtype ItemType

    var count: Int { get }

    mutating func append(_ item: ItemType)

    subscript(i: Int) -> ItemType { get }

}


struct IntStack: Container {

    var items = [Int]()

    var count: Int

    mutating func append(_ item: Int) {

        items.append(item)

    }

    

    subscript(i: Int) -> Int {

        return items[i]

    }

}


프로토콜 구현을 Int 타입으로 한 모습

typealias를 사용하여 ItemType을 어떤 타입으로 사용할지 조금 더 명확히 할 수 있다.


- 제네릭 타입에서는 연관 타입과 타입 매개변수를 대응시킬 수 있다.

 : 연관 타입 부분에 타입 매개변수를 명시함


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]()

    var count: Int {

        return items.count

    }

    mutating func append(_ item: Element) {

        items.append(item)

    }

    subscript(i: Int) -> Element {

        return items[i]

    }

}





[제네릭 서브스크립트]


extension Stack {

    subscript<Indices: Sequence>(indices: Indices) -> [ItemType] where Indices.Iterator.Element == Int {

        var result = [ItemType]()

        for index in indices {

            result.append(self[index])

        }

        return result

    }

}


타입 매개변수 Indices를 두어 매개변수를 제네릭하게 받아들임, Sequence 프로토콜을 준수함

Indices 타입 IteratorElement의 타입이 Int 타입이어야 하는 제약 추가

Indices 타입의 indices 매개변수로 인덱스 값을 받음

indices 시퀀스의 인덱스 값에 해당하는 스택 요소의 값을 배열로 반환

'Swift > 야곰의 스위프트' 카테고리의 다른 글

24장 타입 중첩  (0) 2018.04.28
23장 프로토콜 지향 프로그래밍  (0) 2018.04.28
21장 익스텐션  (0) 2018.03.07
20장 프로토콜  (0) 2018.03.01
19장 타입캐스팅  (0) 2018.03.01
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함