포스트

2주차 (7)

Generic (제네릭)

  • 제네릭으로 구현한 기능과 타입은 재사용하기도 쉽고, 코드의 중복을 줄일 수 있다.
  • 제네릭을 사용하고자 할 때는 제네릭이 필요한 타입 또는 메서드의 이름 뒤의 홀화살괄호 기호 사이에 제네릭을 위한 타입 매개변수를 써주어 제네릭을 사용할 것임을 표시한다.
  • 제네릭은 실제 타입 이름을 써주는 대신에 placeholder를 사용한다. [ eg: T, V, U ]
  • placeholder는 타입의 종류를 알려주지 않지만 어떤 타입이라는 것은 알려준다.
  • placeholder의 실제 타입은 함수가 호출되는 순간 결정된다.
  • placeholder는 타입 매개변수로 쓰일 수도 있는데, 이 타입 매개변수는 함수를 호출할 때마다 실제 타입으로 치환된다.
  • 하나의 타입 매개변수를 갖지 않고 여러 개의 타입 매개변수를 갖고 싶다면 홀화살괄호 기호 안쪽에 쉼표로 분리한 여러 개의 타입 매개변수를 지정해줄 수 있다. [ eg: <T, U> ]
  • 제네릭 타입을 구현하면 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있다.
  • 제네릭 타입을 정해주면 그 타입에만 동작하도록 제한할 수 있어 안전하고 의도한 대로 기능을 사용하도록 유도할 수 있다.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
inout 키워드는 함수 내에서 매개변수로 전달된 값을 변경하고, 
이를 함수 외부에서도 반영할 수 있도록 하는 데 사용된다. 
이를 통해 함수 내에서 매개변수의 값을 직접 수정할 수 있다.

inout 키워드 사용 방법:
1. 매개변수에 inout 키워드를 붙여 선언한다.
2. 함수 호출 시 매개변수를 & 기호로 전달하여 해당 값을 참조로 전달한다.
*/


// 함수 정의
func increment(_ value: inout Int) {
    value += 1
}

var number = 5
print("Before increment: \(number)") // 출력: Before increment: 5

// 함수 호출 시 매개변수에 &를 사용하여 변수의 참조를 전달
increment(&number)

print("After increment: \(number)") // 출력: After increment: 6

// 두 변수의 값을 바꿔주는 함수를 타입별로 작성해야함(제네릭 사용 X)
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
}

// 이전에 배웠던 큐, 스택을 다시 살펴보자
struct Queue<T> {
    private var queue: [T] = []
    
    public var count: Int {
        return queue.count
    }
    
    public var isEmpty: Bool {
        return queue.isEmpty
    }
    
    public mutating func enqueue(_ element: T) {
        queue.append(element)
    }
    
    public mutating func dequeue() -> T? {
        return isEmpty ? nil : queue.removeFirst()
    }
}

var queue = Queue<Int>()
queue.enqueue(10)
queue.enqueue(20)
queue.dequeue() // 10

struct Stack<T> {
    private var stack: [T] = []
    
    public var count: Int {
        return stack.count
    }
    
    public var isEmpty: Bool {
        return stack.isEmpty
    }
    
    public mutating func push(_ element: T) {
        stack.append(element)
    }
    
    public mutating func pop() -> T? {
        return isEmpty ? nil : stack.popLast()
    }
}

var stack = Stack<Int>()
stack.push(10)
stack.push(20)
stack.pop() // 20

// 딕셔너리 예시
@frozen public struct Dictionary<Key, Value> where Key : Hashable {

    /// The element type of a dictionary: a tuple containing an individual
    /// key-value pair.
    public typealias Element = (key: Key, value: Value)


var fruitsInventory: Dictionary<String, Int> = [:]
fruitsInventory["apple"] = 3
/*
Key, Value 타입의 제네릭으로 되어있어 우리는 원하는 타입으로 딕셔너리를 생성할 수 있음
제약조건은 Key가 Hashable 프로토콜만 따르면 되는 것이다
기본 자료형인 String은 Hashable 프로토콜을 따르고 있음
만약 다른 자료형을 Key로 사용하려면 Hashable 프로토콜을 채택해야 함
*/

/*
where 란?

제네릭의 제약조건(Constraints)인 where 키워드는 제네릭 타입에 특정 조건을 부여하여 
해당 제약을 충족하는 타입만을 사용할 수 있도록 하는 기능이다. 
where 키워드를 사용하여 제네릭 타입에 특정 프로토콜 채택, 
특정 타입과의 상속 관계 등을 제한할 수 있다.
*/

// 프로토콜 채택 제약 예시
func process<T>(value: T) where T: Numeric {
    // Numeric 프로토콜을 채택하는 타입만을 제네릭 타입 T로 받음
    print("Value is a numeric type.")
}

process(value: 5) // 출력: Value is a numeric type.
process(value: 3.14) // 출력: Value is a numeric type.
// process(value: "Hello") // 컴파일 에러 - 문자열은 Numeric 프로토콜을 채택하지 않음


// 클래스의 상속 관계 제약 예시
class MyClass {}
class MySubclass: MyClass {}

func process<T>(value: T) where T: MyClass {
    print("Value is an instance of MyClass or its subclasses.")
}

let obj = MySubclass()
process(value: obj) // 출력: Value is an instance of MyClass or its subclasses.
// process(value: "Hello") // 컴파일 에러 - 문자열은 MyClass 또는 그 하위 클래스가 아님
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.