[Swift 공식문서 읽기]Generics

llim🧚🏻‍♀️·2021년 8월 25일
1

Swift

목록 보기
22/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


제네릭

제네릭은 더 유연하고 재사용 가능한 함수와 타입을 작성할 수 있도록 해줍니다.

제네릭이 풀려고 하는 문제

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
	let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

만약 Int형이 아니라 String 값을 변경하려면 어떻게 해야할까요? 혹은 Double이라면요?

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

각 함수는 인자값의 타입만 다르고 함수의 내용을 동일합니다. 지네릭을 사용하면 이렇게 인자값의 타입만 다르고 수행하는 기능이 동일한 것을 하나의 함수로 만들 수 있습니다.

제네릭 함수

제네릭 함수는, 함수명 뒤에 타입 이름 플레이스 홀더를 선언하고 T타입으로 파라미터를 선언합니다. 플레이스 홀더인 T는 타입이 어떤 타입이어야 하는지 명시하지 않습니다. 다만 그것을 사용하는 인자들의 타입 같다는 것을 알려줍니다.

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

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

제네릭 타입

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

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

제네릭 타입의 확장

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

if let topItem = stackOfStrings.topItem {
    print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."

타입 제한

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

위 코드는 에러가 발생합니다. 이유는 value == valueToFind의 코드에서 두 값을 비교하게 되는데 두 값을 비교하는 경우에는 즉, == 등호 메소드를 사용하기 위해서는 두 값 혹은 객체가 반드시 Equatable 프로토콜을 따라야 하기 때문입니다.

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

이 문제를 해결하기 위해 T는 Equatable프로토콜을 따른다고 표시합니다. 타입 플레이스 홀더에 <T: Equatable>같이 적어줍니다. 이것으로 T가 Equatable프로토콜을 따른 다는 것을 알려줍니다.

Associated Types

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

아래 코드에서는 Item을 Int형으로 선언해 사용합니다.

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

아래 예제에서는 Item을 Element형으로 지정해 사용합니다.

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

존재 하는 타입에 연관 타입을 확장

extension Array: Container {}

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

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

연관 타입의 제한

연관 타입을 적용할 수 있는 타입에 조건을 걸어 제한을 둘 수 있습니다. 조건을 붙일때는 where구문을 사용합니다. 이 조건에는 특정 탑입인지의 여부, 특정 프로토콜을 따르는지 여부 등이 사용될 수 있습니다. 다음은 Suffix가 SuffixableContainer프로토콜을 따르고 Item타입이 반드시 Container의 Item타입이어야 한다는 조건을 추가한 것입니다.

(??????????)

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

extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30

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

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
}

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."

extension

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

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

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

예제에서 String은 Equatable프로토콜을 따르므로 true에 해당하는 분기가 실행됩니다. 하지만 만약 NotEquatable와 같이 Equatable을 따르지 않는 Stack에서 익스텐션에 선언된 함수 isTop을 실행하면 에러가 발생하는 것을 확인할 수 있습니다.

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절에서 특정 프로토콜을 따르는 것 뿐 아니라 특정 값 타입인지 비교하는 구분을 사용할 수도 있습니다.

연관 타입 + 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
}
protocol ComparableContainer: Container where Item: Comparable { }

서브스크립트

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

오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글