[Swift] Generics

Lena·2020년 12월 16일
0
post-custom-banner

Generics

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 Functions

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이 결정된다.

Type Parameters

타입 파라미터는 여러 개를 사용할 수도 있다.

Generic Types

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)
        })
}

Type Constraints

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 프로토콜을 따르도록 제한하여 에러를 해결할 수 있다.

Associated Types

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 InStackContainer protocol을 채택한 경우의 코드는 다음과 같다. associatedtype Itemtypealias로 정의해준다.

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]
    }
}

Generic Where Clauses

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
}

Extensions with a Generic Where Clause

extension에서도 where 절을 사용할 수 있다.

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

Reference

https://docs.swift.org/swift-book/LanguageGuide/Generics.html

post-custom-banner

0개의 댓글