Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code.
제네릭은 유연하고 재사용 가능한 코드를 만들기 위한 방법으로 타입을 추상화하는 것이다.
스위프트의 Array
Dictionary
type이 대표적인 generic collection이다.
generic function은 인자의 실제 타입이 아닌 placeholder type name(아래 예제에서는 T)을 사용하여 표현하고, function name 뒤에 <T>
를 붙여 T가 실제 타입이 아닌 placeholder임을 명시한다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
타입 파라미터 T는 function이 call 될 때 실제 type이 결정된다.
타입 파라미터는 여러 개를 사용할 수도 있다.
generic type의 class, struct, enum을 정의할 수도 있다(in a similar way to Array and Dictionary).
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
다음은 generic enumeration인 Result<Success, Failure>
를 이용한 예제이다.
// Generic Enumeration
@frozen enum Result<Success, Failure> where Failure : Error
// database로부터 user data를 가져오기
public func getAllUsers(completion: @escaping (Result<[User], Error>) -> Void) {
database.child("users").observeSingleEvent(of: .value, with: { snapshot in
// if snapshot.value as? User
// success
completion(.success)
// ...or not
// fail
completion(.failure)
})
}
generic type이 특정 protocol을 따르도록 제한할 수 있다.
// syntax
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
// example
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind { //
return index
}
}
return nil
}
equal 연산자(==)는 Equatable
프로토콜을 준수하는 타입에만 사용할 수 있기 때문에, 제네릭 타입 T가 Equatable
프로토콜을 따르도록 제한하여 에러를 해결할 수 있다.
generic type을 정의하는 것처럼, protocol을 정의할 때도 associated types을 선언하는 것이 유용할 수 있다. associated type은 protocol 내에서 사용될 type의 placeholder name이다.
associated type의 실제 타입은 protocol이 실제 채택되기 전까지는 정해지지 않는다.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
nongeneric InStack
이 Container
protocol을 채택한 경우의 코드는 다음과 같다. associatedtype Item
을 typealias
로 정의해준다.
struct IntStack: Container {
// original IntStack implementation
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
generic Stack
이 프로토콜을 준수한 경우는 다음과 같다.
Element
가 associatedtype인 Item
으로 추론된다.
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
type constraints으로 generic function, type에 제한을 두는 것처럼, where 키워드를 사용해 associated types에 조건을 줄 수 있다.
You write a generic where clause right before the opening curly brace of a type or function’s body.
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
}
extension에서도 where 절을 사용할 수 있다.
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
https://docs.swift.org/swift-book/LanguageGuide/Generics.html