관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift 공식 문서] 22. Generics (제네릭스) 본문

swift 공식 문서

[iOS - swift 공식 문서] 22. Generics (제네릭스)

jake-kim 2021. 7. 21. 22:43

Generics

  • 제네릭스를 사용하는 목적
    • 유연하고 재사용 가능한 함수 작성
    • 중복을 피하고 그 의도를 명확하고 추상적인 방식으로 표현하는 코드 작성

Generics으로 해결할 수 있는 문제

  • swap 함수
// non-generics
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// generics
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

generics를 사용하여 linked-list 구현

  • Node 정의
public class Node<T> {
    var value: T
    var next: Node?
    weak var previous: Node? // weak가 아니면 순한참조 발생

    public init(value: T) {
    	self.value = value
    }
}
  • Linked List 클래스 정의
public class LinkedList<T> {
    private var head: Head?
    
    public var isEmpty: Bool {
        return head == nil
    }
    
    public var first: Node? {
        return head
    }
    
    public var last: Node? {
        guard var node = head else {
            return nil
        }
        while let next = node.next {
            node = next
        }
        return node
    }
}
  • append
public func append(value: T) {
    let newNode = Node(value: value)
    if let lastNode = last {
        newNode.previous = lastNode
        lastNode.next = newNode
    } else {
        head = newNode
    }
}
  • index로 노트 탐색
public func node(at index: Int) -> Node {
    if index == 0 {
        return head!
    } else {
        var node = head!.next
        for _ in 1..<index {
            node = node?.next
            if node == nil {
                break
            }
        }
        return node!
    }
}
  • insert at index
public func insert(value: T, at index: Int) {
    let newNode = Node(value: value)
    if index == 0 {
        newNode.next = head
        head?.previous = newNode
        head = newNode
    } else {
        let previousNode = self.node(at: index - 1)
        let nextNode = previousNode.next
        
        newNode.previous = previousNode
        newNode.nextNode = previousNode.next
        previousNode.next = newNode
        nextNode?.previous = newNode
    }
}
  • remove at
public func remove(node: Node) -> T {
    let previousNode = node.previous
    let nextNode = node.next
    
    if let previousNode = previousNode {
        previousNode.next = nextNode
    } else {
        head = nextNode
    }
    nextNode?.previous = previousNode
    
    node.previous = nil
    node.next = nil
    return node.value
}

public func removeAt(_ index: Int) -> T {
    let nodeToRemove = node(at: index)
    return remove(node: nodeToRemove)
}

Generics 제한 조건

  • T와 U라는 Generics에 특정 조건 부여
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

Associated Type

  • protocol에서 제네릭과같은 개념
    • protocol이 채택될때 유형이 지정
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
  • associatedtype에 제약 추가
protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Generics에서의 where closure

  • C1 타입은 Container 프로토콜을 준수: C1: Container
  • C2 타입은 Container 프로토콜을 준수: C2: Container
  • C1.Item과 C2.Item은 같은 타입: C1.Item == C2.Item
  • C1I.tem은  Equatable 프로토콜을 준수: C1.Item: Equatable
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으로 Generics + where

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

Context Where - method에서 작성

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

Subscript에서의 generics where

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

* 참고

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

https://itnext.io/linkedlist-in-swift-code-a-linkedlist-data-structure-in-swift-playgrounds-97fe2ed9b8f1

Comments