블로그

[Swift] Generics in Documents

게시 11분

목차

  1. The Problem That Generics Solve
  2. Generic Functions
  3. Type Parameters
  4. Naming Type Parameters
  5. Generic Types
  6. Extending a Generic Type
  7. Type Constraints
  8. Type Constraint Syntax
  9. Type Constraints in Action
  10. Associated Types
  11. Associated in Action
  12. Extending an Existing Type to Specify an Associated Type
  13. Adding Constraints to an Associated Type
  14. Using a Protocol in Its Associated Type's Constraints
  15. Generic Where Caluses
  16. Extensions with a Generic Where Clause
  17. Contextual Where Clauses
  18. Associated Types with a Generic Where Clause
  19. 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 } }

쓰던 방식 그대로 서브스크립트에도 적용가능하다.

댓글

댓글을 남기면 닉네임(또는 GitHub 계정 정보)이 저장됩니다. 개인정보처리방침

GitHub