λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ₯– Bread Basics/Swift

Swift 곡식 λ¬Έμ„œ 정리 - μ œλ„ˆλ¦­ (Generics)

by BreadDev 2025. 4. 13.
728x90

μ•ˆλ…•ν•˜μ„Έμš”. μ˜€λŠ˜μ€ Swift 'μ œλ„ˆλ¦­(Generics)'에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

πŸ“Œ μ œλ„ˆλ¦­μ΄λž€? 그리고 μ™œ ν•„μš”ν• κΉŒμš”?

μ œλ„ˆλ¦­ μ½”λ“œλŠ” νŠΉμ • νƒ€μž…μ— ꡬ애받지 μ•Šκ³ , μ •μ˜ν•œ μš”κ΅¬μ‚¬ν•­μ„ μΆ©μ‘±ν•˜λŠ” λͺ¨λ“  νƒ€μž…μ— λŒ€ν•΄ λ™μž‘ν•  수 μžˆλŠ” μœ μ—°ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 있게 ν•΄μ€λ‹ˆλ‹€. Swift의 Array, Dictionary와 같은 ν‘œμ€€ 라이브러리의 λŒ€λΆ€λΆ„μ€ μ œλ„ˆλ¦­μœΌλ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

μ œλ„ˆλ¦­μ΄ ν•΄κ²°ν•˜λŠ” 문제

μ•„λž˜ 예제λ₯Ό 톡해 μ œλ„ˆλ¦­μ˜ ν•„μš”μ„±μ„ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€:

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

이 ν•¨μˆ˜λŠ” 두 μ •μˆ˜μ˜ 값을 κ΅ν™˜ν•˜λŠ” κ°„λ‹¨ν•œ ν•¨μˆ˜μž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λ§Œμ•½ λ¬Έμžμ—΄μ΄λ‚˜ μ‹€μˆ˜ 값을 κ΅ν™˜ν•˜κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”?

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
}

μ„Έ ν•¨μˆ˜μ˜ 본문이 μ™„μ „νžˆ λ™μΌν•˜λ‹€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 이런 μ½”λ“œ 쀑볡은 μ œλ„ˆλ¦­μ„ 톡해 ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ μ œλ„ˆλ¦­ ν•¨μˆ˜ μž‘μ„±ν•˜κΈ°

μ œλ„ˆλ¦­ ν•¨μˆ˜λŠ” λͺ¨λ“  νƒ€μž…μ— λŒ€ν•΄ λ™μž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μœ„μ˜ ν•¨μˆ˜λ“€μ„ μ œλ„ˆλ¦­μœΌλ‘œ λ‹€μ‹œ μž‘μ„±ν•΄ λ΄…μ‹œλ‹€:

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λŠ” 이제 107, anotherIntλŠ” 이제 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString은 이제 "world", anotherString은 이제 "hello"

μ°Έκ³ : μ‹€μ œλ‘œ Swift ν‘œμ€€ λΌμ΄λΈŒλŸ¬λ¦¬μ—λŠ” swap(_:_:) ν•¨μˆ˜κ°€ 이미 κ΅¬ν˜„λ˜μ–΄ μžˆμ–΄, 직접 κ΅¬ν˜„ν•  ν•„μš” 없이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

νƒ€μž… νŒŒλΌλ―Έν„° (Type Parameters)

<T>μ—μ„œ TλŠ” νƒ€μž… νŒŒλΌλ―Έν„°μž…λ‹ˆλ‹€. ν•¨μˆ˜κ°€ 호좜될 λ•Œλ§ˆλ‹€ μ‹€μ œ νƒ€μž…μœΌλ‘œ λŒ€μ²΄λ©λ‹ˆλ‹€. νƒ€μž… νŒŒλΌλ―Έν„°μ˜ 이름은 λŒ€κ°œ λŒ€λ¬Έμžλ‘œ μ‹œμž‘ν•˜λ©°, T, U, V 같은 단일 λ¬Έμžλ‚˜ Element, Key, Value와 같은 μ„€λͺ…적인 이름을 μ‚¬μš©ν•©λ‹ˆλ‹€.

μ—¬λŸ¬ νƒ€μž… νŒŒλΌλ―Έν„°λ₯Ό μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ <T, U, V>와 같이 콀마둜 κ΅¬λΆ„ν•˜μ—¬ λ‚˜μ—΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ μ œλ„ˆλ¦­ νƒ€μž… μ •μ˜ν•˜κΈ°

ν•¨μˆ˜λΏλ§Œ μ•„λ‹ˆλΌ 클래슀, ꡬ쑰체, μ—΄κ±°ν˜•λ„ μ œλ„ˆλ¦­μœΌλ‘œ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒμ€ μ œλ„ˆλ¦­ μŠ€νƒ(Stack) κ΅¬ν˜„μ˜ μ˜ˆμž…λ‹ˆλ‹€:

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")
// μŠ€νƒμ—λŠ” 이제 4개의 λ¬Έμžμ—΄μ΄ 있음

let fromTheTop = stackOfStrings.pop()
// fromTheTop은 "cuatro"와 κ°™μŒ

μ œλ„ˆλ¦­ νƒ€μž… ν™•μž₯ν•˜κΈ°

μ œλ„ˆλ¦­ νƒ€μž…μ„ ν™•μž₯ν•  λ•ŒλŠ” νƒ€μž… νŒŒλΌλ―Έν„° λͺ©λ‘μ„ λ‹€μ‹œ μž‘μ„±ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€:

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

이 ν™•μž₯을 톡해 λͺ¨λ“  Stack μΈμŠ€ν„΄μŠ€μ— topItem 속성이 μΆ”κ°€λ©λ‹ˆλ‹€:

if let topItem = stackOfStrings.topItem {
    print("μŠ€νƒμ˜ μ΅œμƒμœ„ ν•­λͺ©μ€ \(topItem)μž…λ‹ˆλ‹€.")
}
// "μŠ€νƒμ˜ μ΅œμƒμœ„ ν•­λͺ©μ€ tresμž…λ‹ˆλ‹€." 좜λ ₯

πŸ“Œ νƒ€μž… μ œμ•½ (Type Constraints)

λ•Œλ‘œλŠ” μ œλ„ˆλ¦­ ν•¨μˆ˜λ‚˜ νƒ€μž…μ΄ νŠΉμ • ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜κ±°λ‚˜ νŠΉμ • 클래슀λ₯Ό μƒμ†ν•˜λŠ” νƒ€μž…μ—λ§Œ μž‘λ™ν•˜λ„λ‘ μ œν•œν•΄μ•Ό ν•  ν•„μš”κ°€ μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Swift의 DictionaryλŠ” ν‚€ νƒ€μž…μ΄ Hashable ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ νƒ€μž… μ œμ•½μ„ μ‚¬μš©ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€:

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

이 ν•¨μˆ˜λŠ” Tκ°€ Equatable ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜λŠ” νƒ€μž…μ΄μ–΄μ•Όλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 덕뢄에 == μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•  수 있게 λ©λ‹ˆλ‹€:

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndexλŠ” nil(배열에 9.3이 μ—†κΈ° λ•Œλ¬Έ)

let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndexλŠ” 2의 값을 κ°€μ§„ Int?

πŸ“Œ μ—°κ΄€λœ νƒ€μž… (Associated Types)

ν”„λ‘œν† μ½œμ„ μ •μ˜ν•  λ•Œ ν•˜λ‚˜ μ΄μƒμ˜ μ—°κ΄€λœ νƒ€μž…μ„ μ„ μ–Έν•˜λŠ” 것이 μœ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ—°κ΄€λœ νƒ€μž…μ€ ν”„λ‘œν† μ½œμ˜ μΌλΆ€λ‘œ μ‚¬μš©λ˜λŠ” νƒ€μž…μ— λŒ€ν•œ ν”Œλ ˆμ΄μŠ€ν™€λ” 이름을 μ œκ³΅ν•©λ‹ˆλ‹€.

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

이 ν”„λ‘œν† μ½œμ€ μ»¨ν…Œμ΄λ„ˆκ°€ μ–΄λ–€ νƒ€μž…μ˜ ν•­λͺ©μ„ μ €μž₯ν• μ§€ μ§€μ •ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λŒ€μ‹ , ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜λŠ” νƒ€μž…μ΄ Item의 μ‹€μ œ νƒ€μž…μ„ μ§€μ •ν•©λ‹ˆλ‹€:

struct IntStack: Container {
    // μ›λž˜ IntStack κ΅¬ν˜„
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    
    // Container ν”„λ‘œν† μ½œ μ€€μˆ˜
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

μ œλ„ˆλ¦­ νƒ€μž…μ—μ„œλŠ” μ—°κ΄€ νƒ€μž…μ΄ μžλ™μœΌλ‘œ μΆ”λ‘ λ©λ‹ˆλ‹€:

struct Stack<Element>: Container {
    // μ›λž˜ Stack<Element> κ΅¬ν˜„
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    
    // Container ν”„λ‘œν† μ½œ μ€€μˆ˜
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

μ—°κ΄€λœ νƒ€μž…μ— μ œμ•½ μΆ”κ°€ν•˜κΈ°

μ—°κ΄€λœ νƒ€μž…μ—λ„ νƒ€μž… μ œμ•½μ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

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

πŸ“Œ μ œλ„ˆλ¦­ Where 절 (Generic Where Clauses)

νƒ€μž… μ œμ•½ 외에도, μ œλ„ˆλ¦­ ν•¨μˆ˜λ‚˜ νƒ€μž…μ˜ μš”κ΅¬μ‚¬ν•­μ„ 더 μ„ΈλΆ€μ μœΌλ‘œ μ •μ˜ν•˜κΈ° μœ„ν•΄ μ œλ„ˆλ¦­ where μ ˆμ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {
        
        // 두 μ»¨ν…Œμ΄λ„ˆμ˜ ν•­λͺ© μˆ˜κ°€ 같은지 확인
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // 각 ν•­λͺ©μ΄ λ™μΌν•œμ§€ 확인
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // λͺ¨λ“  ν•­λͺ©μ΄ μΌμΉ˜ν•˜λ©΄ true λ°˜ν™˜
        return true
}

이 ν•¨μˆ˜λŠ” 두 μ»¨ν…Œμ΄λ„ˆκ°€ 같은 ν•­λͺ© νƒ€μž…μ„ κ°€μ§€κ³  있으며, κ·Έ ν•­λͺ© νƒ€μž…μ΄ Equatable을 μ€€μˆ˜ν•΄μ•Ό ν•œλ‹€λŠ” μ œμ•½μ„ μ„€μ •ν•©λ‹ˆλ‹€:

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

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

if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("λͺ¨λ“  ν•­λͺ©μ΄ μΌμΉ˜ν•©λ‹ˆλ‹€.")
} else {
    print("μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” ν•­λͺ©μ΄ μžˆμŠ΅λ‹ˆλ‹€.")
}
// "λͺ¨λ“  ν•­λͺ©μ΄ μΌμΉ˜ν•©λ‹ˆλ‹€." 좜λ ₯

Where 절이 μžˆλŠ” ν™•μž₯

where μ ˆμ„ μ‚¬μš©ν•΄ μ œλ„ˆλ¦­ νƒ€μž…μ„ ν™•μž₯ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€:

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

이 ν™•μž₯은 Stack의 μš”μ†Œκ°€ Equatable을 μ€€μˆ˜ν•  λ•Œλ§Œ μ μš©λ©λ‹ˆλ‹€:

if stackOfStrings.isTop("tres") {
    print("μ΅œμƒμœ„ μš”μ†ŒλŠ” tresμž…λ‹ˆλ‹€.")
} else {
    print("μ΅œμƒμœ„ μš”μ†ŒλŠ” λ‹€λ₯Έ κ²ƒμž…λ‹ˆλ‹€.")
}
// "μ΅œμƒμœ„ μš”μ†ŒλŠ” tresμž…λ‹ˆλ‹€." 좜λ ₯

ν”„λ‘œν† μ½œ ν™•μž₯에도 where μ ˆμ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

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

λ˜ν•œ, νŠΉμ • νƒ€μž…μ—λ§Œ μ μš©λ˜λŠ” ν™•μž₯도 κ°€λŠ₯ν•©λ‹ˆλ‹€:

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())
// 648.9 좜λ ₯

상황별 Where 절

μ œλ„ˆλ¦­ νƒ€μž…μ˜ μ»¨ν…μŠ€νŠΈμ—μ„œ 이미 μž‘μ—… 쀑인 경우, κ°œλ³„ λ©”μ„œλ“œλ‚˜ 속성에 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
    }
}

πŸ“Œ μ œλ„ˆλ¦­ μ„œλΈŒμŠ€ν¬λ¦½νŠΈ

μ„œλΈŒμŠ€ν¬λ¦½νŠΈλ„ μ œλ„ˆλ¦­μœΌλ‘œ μ •μ˜ν•  수 있으며, μ œλ„ˆλ¦­ 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
    }
}

이 μ„œλΈŒμŠ€ν¬λ¦½νŠΈλŠ” μ •μˆ˜ μ‹œν€€μŠ€λ₯Ό λ°›μ•„ ν•΄λ‹Ή μΈλ±μŠ€μ— μžˆλŠ” ν•­λͺ©λ“€μ˜ 배열을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

πŸ“Œ 정리

Swift의 μ œλ„ˆλ¦­μ€ νƒ€μž… μ•ˆμ „μ„±μ„ μœ μ§€ν•˜λ©΄μ„œ μ½”λ“œ μž¬μ‚¬μš©μ„±μ„ λ†’μ΄λŠ” κ°•λ ₯ν•œ κΈ°λŠ₯μž…λ‹ˆλ‹€. μ œλ„ˆλ¦­μ„ 톡해:

  1. μ½”λ“œ 쀑볡을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€ - λ‹€μ–‘ν•œ νƒ€μž…μ— λŒ€ν•΄ λ™μΌν•œ κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” μ½”λ“œλ₯Ό ν•œ 번만 μž‘μ„±ν•˜λ©΄ λ©λ‹ˆλ‹€.
  2. νƒ€μž… μ•ˆμ „μ„±μ„ 보μž₯ν•©λ‹ˆλ‹€ - 컴파일 μ‹œμ μ— νƒ€μž… 검사가 이루어져 λŸ°νƒ€μž„ 였λ₯˜λ₯Ό 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.
  3. μœ μ—°ν•œ APIλ₯Ό 섀계할 수 μžˆμŠ΅λ‹ˆλ‹€ - μ‚¬μš©μžκ°€ λ‹€μ–‘ν•œ νƒ€μž…κ³Ό ν•¨κ»˜ APIλ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.
  4. μ„±λŠ₯ μ €ν•˜ 없이 좔상화λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€ - μ œλ„ˆλ¦­μ€ 컴파일 μ‹œκ°„μ— ν•΄κ²°λ˜λ―€λ‘œ λŸ°νƒ€μž„ μ„±λŠ₯에 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.