Generic (제네릭)
- 제네릭으로 구현한 기능과 타입은 재사용하기도 쉽고, 코드의 중복을 줄일 수 있다.
- 제네릭을 사용하고자 할 때는 제네릭이 필요한 타입 또는 메서드의 이름 뒤의 홀화살괄호 기호 사이에 제네릭을 위한 타입 매개변수를 써주어 제네릭을 사용할 것임을 표시한다.
- 제네릭은 실제 타입 이름을 써주는 대신에 placeholder를 사용한다. [ eg: T, V, U ]
- placeholder는 타입의 종류를 알려주지 않지만 어떤 타입이라는 것은 알려준다.
- placeholder의 실제 타입은 함수가 호출되는 순간 결정된다.
- placeholder는 타입 매개변수로 쓰일 수도 있는데, 이 타입 매개변수는 함수를 호출할 때마다 실제 타입으로 치환된다.
- 하나의 타입 매개변수를 갖지 않고 여러 개의 타입 매개변수를 갖고 싶다면 홀화살괄호 기호 안쪽에 쉼표로 분리한 여러 개의 타입 매개변수를 지정해줄 수 있다. [ eg: <T, U> ]
- 제네릭 타입을 구현하면 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있다.
- 제네릭 타입을 정해주면 그 타입에만 동작하도록 제한할 수 있어 안전하고 의도한 대로 기능을 사용하도록 유도할 수 있다.
/*
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 또는 그 하위 클래스가 아님
PREVIOUS2주차 (6)
NEXTByteCoin (2)