티스토리 뷰

iOS/Swift

[Swift] Generic (2)

HarryJeonn 2022. 11. 21. 16:04

[Swift] Generic (2)

저번 글에 이어서 제네릭에 대해서 더 알아보자

연관 타입 (Associated Types)

연관 타입은 프로토콜의 일부분으로 타입에 플레이스홀더 이름을 부여한다.

다시말해 특정 타입을 동적으로 지정해 사용할 수 있다.

연관 타입의 실 사용 (Associated Types in Action)

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

위와 같이 associatedtype을 사용할 수 있다.

이렇게 지정하면 Item은 어떤 타입도 될 수 있다.

struct IntStack: Container {
    var items = [Int]()

    mutating func push(_ item: Int) {
        items.append(item)
    }

    mutating func pop() -> Int {
        return items.removeLast()
    }

    typealias Item = Int

    mutating func append(_ item: Int) {
        self.push(item)
    }

    var count: Int {
        return items.count
    }

    subscript(i: Int) -> Int {
        return items[i]
    }
}

위 코드에서는 Item을 Int형으로 선언해 사용했다.

Element형으로 지정하여 바꿔보자.

struct Stack<Element>: Container {
    var items = [Element]()

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element {
        return items.removeLast()
    }

    typealias Item = Element

    mutating func append(_ item: Element) {
        self.push(item)
    }

    var count: Int {
        return items.count
    }

    subscript(i: Int) -> Element {
        return items[i]
    }
}

존재 하는 타입에 연관 타입을 확장 (Extending an Existing Type to Specify an Associated Type)

extension Array: Container {}

위와 같이 기존의 타입 Array에 extension을 사용하여 특정 연관타입을 추가할 수 있다.

이것이 가능한 이유는 Array타입은 Container에 선언된 append, count, subscript가 모두 정의되어 있기 때문이다.

프로토콜에 연관 타입의 제한 사용하기 (Using a Protocol in Its Associated Type’s Constraints)

연관 타입을 적용할 수 있는 타입에 조건을 걸어 제한을 둘 수 있다. 조건을 붙일 때는 where구문을 사용한다. 이 조건에는 특정 타입인지, 특정 프로토콜을 따르는지 등의 여부를 알 수 있다.

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

위 코드는 Suffix가 SuffixableContainer 프로토콜을 따르고 Item타입이 반드시 Container의 Item타입이어야 한다는 조건을 추가한 것이다.

위에서 Stack의 연관 타입 Suffix 또한 Stack이다. Stack의 suffix의 실행으로 또 다른 Stack을 반환하게 된다.

그래서 IntStack에 Stack을 사용해 SuffixableContainer을 따르는 extension을 선언할 수도 있다.

extension IntStack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack<Int> {
        var result = Stack<Int>()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack<Int>.
}

제네릭의 where절 (Generic Where Clauses)

제네릭에서도 where절을 사용할 수 있다.

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
}

Container C1과 C2를 비교하여 모든 값이 같을 때 true를 반환하는 코드이다.

위에서 Container가 달라도 상관없다. 단지 각 Container의 같은 인덱스의 모든 값이 같다면 true를 얻게 된다.

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("All items match.")
} else {
    print("Not all items match.")
}
// Prints "All items match."

같은 값을 가지고 있지만 하나는 Stack, 하나는 Array Container이다.

Container가 다르지만 값이 같기 때문에 true를 반환한다.

Where절을 포함하는 제네릭의 익스텐션 (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
    }
}

위 코드는 제네릭(Stack)에 extension으로 isTop 함수를 추가하면서 이 함수가 추가되는 Stack은 반드시 Equatable 프로토콜을 따라야한다고 제한을 부여한 코드이다.

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."

String은 Equatable 프로토콜을 따르기때문에 true에 해당하는 분기가 실행된다.

Equatable 프로토콜을 따르지 않는다면 어떻게 될지 한번 만들어보자

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error

Equatable을 따르지 않는 Stack에서 isTop함수를 실행하면 에러가 발생한다.

extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

위 코드는 Container의 Item이 Equatable을 따라야 한다고 제한을 부여한 코드이다.

startsWith 함수의 인자인 Item은 Container의 특정 아이템이 입력한 Item으로 시작하는지 비교하기 위해서는 Container의 첫 아이템이 입력한 Item과 같은지 비교해야 하기 때문에 Equatable프로토콜을 따라야 한다.

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// Prints "Starts with something else."

Container 프로토콜을 따르는 Array에서 startsWith 함수를 실행한다.

Int 값인 42는 Equatable 프로토콜을 따르므로 startsWith 함수가 실행되고 42는 배열의 첫 번째 값인 9과 같지 않기 때문에 같지 않다는 분기가 실행된다.

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"

where 절에서 특정 프로토콜을 따르는 것 뿐만 아니라 특정 값 타입인지 비교하는 구분을 사용할 수도 있다.

위는 Item이 Double형인지 비교한 코드이다.

Container의 Item [1260.0, 1200.0, 98.6, 37.0]이 Double형이기 때문에 익스텐션에서 구현된 average()를 사용할 수 있습니다.

제네릭의 연관 타입에 where절 적용 (Associated Types with a Generic Where Clause)

연관 타입에도 where절을 적용할 수 있다.

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
}

연관 타입 Iterator에 Iterator의 Element가 Item과 같아야 한다는 조건을 건 예다.

protocol ComparableContainer: Container where Item: Comparable { }

다른 프로토콜을 상속하는 프로토콜에도 where절로 조건을 부여할 수 있다.

제네릭의 서브스크립트 (Generic Subcript)

제네릭의 서브스크립트에도 조건을 걸 수 있다.

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

위 예제는 Indices.Iterator.Element가 Int 형이어야 한다는 조건을 건 예다.

'iOS > Swift' 카테고리의 다른 글

[Swift] Strong, Weak, Unowned 참조  (0) 2022.12.05
[Swift] Delegate, Notification 차이  (0) 2022.11.22
[Swift] Generic (1)  (0) 2022.11.19
[Swift] GCD(Grand Central Dispatch)  (0) 2022.11.14
[Swift] Class와 Struct의 차이  (0) 2022.11.09
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함