Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
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
Archives
Today
Total
관리 메뉴

주니곰의 괴발노트

Swift - Generic 본문

iOS

Swift - Generic

Jhe2 2023. 6. 2. 22:45

 Generic

 모든 타입에서 동작할 수 있는 유연하고 재사용 가능한 함수와 타입을 작성할 수 있음

중복 작성을 피하면서 명확하고 추상적인 방법으로 의도를 표현하는 코드를 작성할 수 있게 해줌

Swift 표준 라이브러리 대부분은 Generic 코드로 되어 있음 (ex. Array, Dictionary 등)

 

 

 사용 예시 및 Generic이 해결하는 문제점들

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"

위 예시는 두 프로퍼티에 있는 값을 바꾸는 메서드

전제 조건은 파라미터 a와 b는 무조건 같은 타입이어야 함

다른 타입의 파라미터를 서로 바꿀 수는 없음

 

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
}

 Double이나 String 처럼 다른 타입을 바꾸는 기능을 구현하려면 위와 같지만 파라미터의 타입이 다른 여러 메서드들을 구성해야 함

 

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

Generic 함수는 위와 같은 문제를 해결해줌

함수 본문 구성은 동일하나 파라미터 타입을 Int, Double 대신 임의의 타입으로 넣어 활용

T의 실제 타입은 메서드가 호출될 때 결정됨

파라미터 입력 전에 <Type> 처럼 꺽쇠 괄호 내에 사용하려고 하는 타입 이름을 명시 후 사용

따라서 T는 임의의 타입이므로 Swift에서 T라는 실제 타입을 찾지 않음

 

 

 타입 파라미터 명명하기

Dictionary와 Array에서 내부적으로 사용되는 타입 파라미터 이름

타입 파라미터 이름은 타입 파라미터와 Generic 타입 간의 관계나 함수 간의 관계를 나타내기 위해 위의 이미지처럼 명명

그러나 의미있는 관계가 없을 경우, T, U, V 처럼 단일 문자를 사용하는 것이 일반적

값이 아니라 타입에 대한 임의의 표시라는 것을 나타내기 위함

 

 

 타입에 제약 조건 걸기

 일반적인 방법

제약 조건 테스트를 위한 기본 설정

위의 예시처럼 <T: 제약조건> 의 형태로 타입에 제약조건을 설정할 수 있음

 

해당 조건에 맞는 인스턴스 삽입

 

 extension과 where 절 활용

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

위 코드는 기존에 있는 Stack에 isTop이라는 메서드를 추가하는 예시

위처럼 where Generic: 채택할 항목 의 형태로 작성하여 제약조건을 추가할 수 있음

 

 상황에 맞게 where 절 활용하기

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

let numbers = [1260, 1200, 98, 37]

print(numbers.average())
// Prints "648.75"

print(numbers.endsWith(37))
// Prints "true"

위 예제는 상황에 맞게 메서드를 적용할 수 있는 코드

Item 이 Int 일 경우 average() 메서드를 사용할 수 있고, Item이 Equatable을 채택한 경우 endsWith(_:) 메서드를 사용할 수 있음

 

 

 타입 제약 동작

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

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"

위 예시는 특정 문자열을 배열에서 찾아 index값을 return하는 함수를 구현

 

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

String 뿐만아니라 다른 타입에 대해서도 같은 동작을 수행하고 싶어 위와 같이 Generic 버전으로 구현시 Compile Error 발생

그 이유는 함수 내에 있는 if value == valueToFind 때문

Swift에서는 모든 타입이 동등 연산자 (==)로 비교할 수 있는 것은 아니기 때문에 실제 타입이 아닌 임시 타입인 T는 비교 연산을 할 수 없음

 

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

따라서 Generic 타입인 T에 Equatable 이라는 제약 조건을 설정하면 T에 대입할 수 있는 타입은 반드시 Equatable 이라는 Protocol을 채택한 타입이어야 하기 때문에 정상적으로 Compile이 가능해짐

 

 

 연관 타입 (Associated Types)

associatedtype은 Protocol 내부에서 사용하는 Generic

해당 protocol이 채택될 때까지 associatedtype은 타입이 지정되지 않음 

 

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

위와 같이 구성해놓은 뒤 다음과 같이 활용

 

struct Stack: Container {
    typealias Item = Int
    
    var items: [Int] = []
    
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

typealias Item = Int 로 Item이라고 정의한 추상적 타입을 Int라는 구체적인 타입으로 변환

그러나 타입추론 덕분에 typealias Item = Int 가 없어도 사용 가능함

 

typealias 설정한 경우
typealias 생략한 경우

Generic 과 마찬가지로 Item에는 한 종류의 타입만 들어가야 함

 

 

 연관 타입 제약 조건

 일반적인 방법

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

위와 같이 associatedtype을 설정할 때 제약조건을 추가할 수 있음

 

 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
}

associatedtype을 설정할 때 where 절을 이용하는 방법도 있음

 

 

 참고

https://bbiguduk.gitbook.io/swift/language-guide-1/generics

https://bbiguduk.gitbook.io/swift/language-guide-1/opaque-types

 

 

 

 

'iOS' 카테고리의 다른 글

Swift - Concurrency  (0) 2023.05.13
SwiftUI - Search Bar 구현  (0) 2023.05.11
Swift - 열거형  (0) 2023.03.12
Swift - Extensions  (0) 2023.03.05
Swift - Concurrency (동시성)  (0) 2023.02.26
Comments