목차
- The Problem That Generics Solve
- Generic Functions
- Type Parameters
- Naming Type Parameters
- Generic Types
- Extending a Generic Type
- Type Constraints
- Type Constraint Syntax
- Type Constraints in Action
- Associated Types
- Associated in Action
- Extending an Existing Type to Specify an Associated Type
- Adding Constraints to an Associated Type
- Using a Protocol in Its Associated Type's Constraints
- Generic Where Caluses
- Extensions with a Generic Where Clause
- Contextual Where Clauses
- Associated Types with a Generic Where Clause
- Generic Subscripts
- The Problem That Generics Solve
타입의 일반화를 위해 Generics 사용.
- Generic Functions
제네릭 함수는 따로 제약조건이 없다면 타입에 상관없이 동작한다.
임의의 타입 T를 사용해 타입을 일반화시킬 수 있다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA }
- Type Parameters
위의 예제에서 T를 타입 파라미터라고 부른다.
- Naming Type Parameter
T가 이름이다. Dictionary<Key, Value>와 같이 Key, Value로 의미있는 이름을 지을 수도 있다.
- Generic Types
struct Stack<Element> { var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") // the stack now contains 4 strings
Stack<Element>로 제네릭 버전으로 타입을 나타낼 수 있다.
- Extending a Generic Type
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } }
다음과같이 extension을 사용해 제네릭 타입을 확장시킬 수 있다.
하지만, 타입 파라미터를 정의할 필요없다.
따로 코드를 작성할 필요없이, Stack 구조체에 있는 타입 파라미터를 그대로 가져와 사용할 수 있다.
- Type Constraints
Equaltable, 즉 == 연산을 사용하려고 하는데 타입 파라미터가 제약없이 사용된다고 가정하면,
컴파일러는 해당 타입이 == 연산함수를 지원하는지 알 수 없기때문에 == 연산을 할 수 없다.
( == 연산을 지원하지 않는 타입들도 존재한다)
때문에 Equaltable을 준수하는 타입만 올것이라는 신호를 넣어줘야하는데, 이를 타입 제약을 통해서 이룬다.
-
Type Constraint Syntax
-
Type Constraints in Action
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // function body goes here }
T: SomeClass와 같이 제약을 넣어줄 수 있다.
SomeClass를 준수 또는 상속하는 타입만 온다고 알려주는것이다.
- Associated Types (in Action)
프로토콜에서 제네릭을 사용하려면 연관타 입을 이용해야한다.
protocol Container<Item> { mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } }
이런식으로 못한다.
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } }
이런식으로 연관값을 선언해서 지원해야한다.
- Extending an Existing Type to Specify an Associated Type
위에서 타입 파라미터를 따로 정의할 필요가 없다는것과 마찬가지로, 연관값도 따로 지정할 필요없다.
- Adding Constraints to an Associated Type
protocol Container { associatedtype Item: Equatable mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } }
타입 파라미터처럼 제약 추가할 수 있다.
- Using a Protocol in Its Associated Type's Constraints
protocol Container { associatedtype Item var items: [Item] { get set } mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix } struct Stack<Element: Equatable>: Container { var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { items.removeLast() } mutating func append(_ item: Element) { push(item) } var count: Int { items.count } subscript(i: Int) -> Element { items[i] } } extension Stack: SuffixableContainer { typealias Suffix = Stack func suffix(_ size: Int) -> Stack { var result = Stack() for index in (count-size)..<count { result.append(self[index]) } return result } }
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
where은 연관값에 제약사항을 추가하는 또다른 방법이라고 적당히 알고,
Suffix.Item == Item이 어떤역할을 하는지에 주목하자.
Suffix 연관값 역시 SuffixableContainer을 준수해야한다.
그리고 SuffixalbeContainer은 Container을 상속하므로 Suffix는 Item 연관값을 가지고있다.
Stack을 확장해주는 부분, Stack<Element>에서 Suffix는 Stack이 되고
Stack에서 타입인자를 따로 지정하지 않았으므로 Suffix.Item은 Element가 되고
Item은 Element 였으므로 서로 같다.
즉, 처음에 Stack을 정의했을 때 넣어준 타입인자와
suffix(_: ) 메서드의 반환값의 제네릭 타입인자가 같도록해주는 제약구문이라고 생각하면 된다.
- Generic Where Caluses
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { // Check that both containers contain the same number of items. if someContainer.count != anotherContainer.count { return false } // Check each pair of items to see if they're equivalent. for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // All items match, so return true. return true }
다음과같이 where절로 타입에 제약조건 넣을 수 있다.
- Extensions with a Generic Where Clause
extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } }
Element가 Equatable을 준수하는 경우에만 확장되도록 할 수 있다.
- Contextual Where Clauses
extension Container { func average() -> Double where Item == Int { var sum = 0.0 for index in 0..<count { sum += Double(self[index]) } return sum / Double(count) } func endsWith(_ item: Item) -> Bool where Item: Equatable { return count >= 1 && self[count-1] == item } }
extension Container where Item == Int { func average() -> Double { var sum = 0.0 for index in 0..<count { sum += Double(self[index]) } return sum / Double(count) } } extension Container where Item: Equatable { func endsWith(_ item: Item) -> Bool { return count >= 1 && self[count-1] == item } }
위 코드와 아래코드는 완전히 같은 의미를 가지는 코드다.
- Associated Types with a Generic Where Clause
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator }
아까 본거
- Generic Subscripts
extension Container { subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result: [Item] = [] for index in indices { result.append(self[index]) } return result } }
쓰던 방식 그대로 서브스크립트에도 적용가능하다.