안녕하세요! 피피아노입니다 🎵
이번 포스팅은 제네릭(Generic)에 대해서 정리해보려고 합니다!
제네릭은 낯설게 들릴 수 있지만, 알고보면 간단한 문법입니다!
그럼 본격적으로 제네릭에 대해서 공부를 해보겠습니다!
Generic이란?
범용 타입이라고도 불리는 제네릭을 번역해보면 '일반적인'이라는 뜻입니다. 이름에서도 유추할 수 있듯이 제네릭은 어떤 타입이든 일반적으로 유연하게 대응할 수 있는 코드를 작성해주는 도구입니다.
비유해서 설명해보자면 제네릭은 마치 '틀'과 같습니다. 무슨 틀이냐구요? 바로 아이스크림 틀이죠. 아이스크림 틀에는 무슨 맛의 아이스크림을 넣든 상관없이 동일한 모양의 아이스크림을 만들 수 있습니다. 제네릭도 마찬가지로, 어떤 타입이 들어오든 그 '틀'에 맞춰 동일한 동작을 수행합니다.
Swift 표준 라이브러리의 대다수는 제네릭으로 선언되어 있을 정도로 중요한 기능입니다!
Generic 함수
제네릭 함수는 타입 매개변수를 사용하여 여러 타입에 작동하는 함수를 정의하게 해줍니다. 이는 함수 정의 시점에는 타입을 명시하지 않고, 함수 호출 시점에 타입을 결정하게 해줍니다.
제네릭 함수의 기본 형태는 다음과 같습니다.
func functionName<T>(parameter: T) {
// function body
}
여기서 'T'는 타입 매개변수로, 실제 타입을 대체합니다. 'T'는 함수 사용자가 제공하는 실제 타입으로 대체됩니다.
예를 들어, 두 개의 값을 교환하는 제네릭 함수는 아래 코드처럼 만들 수 있습니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
이 함수는 Int, String, Double 등 모든 타입의 두 값을 교환하는데 사용할 수 있습니다.
Generic 타입
제네릭 타입은 타입 매개변수를 사용하여 여러 타입에 작동하는 사용자 정의 데이터 타입을 정의하게 해줍니다. 이는 데이터 타입 정의 시점에는 타입을 명시하지 않고, 인스턴스 생성 시점에 타입을 결정하게 해줍니다.
제네릭 타입의 기본 형태는 다음과 같습니다.
struct TypeName<T> {
// type body
}
여기서 'T'는 타입 매개변수로, 실제 타입을 대체합니다. 'T'는 타입 사용자가 제공하는 실제 타입으로 대체됩니다.
예를 들어, 아이템을 저장하는 제네릭 스택 구조체는 아래 코드처럼 만들 수 있습니다.
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.isEmpty ? nil : items.removeLast()
}
}
이 구조체는 Int, String, Double 등 모든 타입의 아이템을 저장하는 데 사용할 수 있습니다.
Generic 사용 이유
제네릭을 사용하는 주요 이유는 2가지가 있는데 우선 코드 재사용과 타입 안전성 때문입니다.
코드 재사용: 제네릭을 사용하면, 특정 타입에 종속되지 않는 유연한 함수나 타입을 만들 수 있습니다. 이를 통해 코드의 재사용성이 향상되며, 중복된 코드를 줄일 수 있습니다. 예를 들어, 두 개의 Integer 값을 교환하는 함수와 두 개의 String 값을 교환하는 함수를 각각 만들 필요 없이, 제네릭을 사용해 두 개의 값을 교환하는 하나의 함수를 만들어 다양한 타입에 대해 사용할 수 있습니다.
타입 안전성: 제네릭을 사용하면, 컴파일 시점에 타입 체크가 가능합니다. 따라서 잘못된 타입의 값이 사용되는 것을 미리 방지할 수 있습니다. 이를 통해 런타임 에러를 줄이고 프로그램의 안정성을 향상시킬 수 있습니다.
Generic 타입 확장
Swift에서 제네릭 타입을 확장하는 방법은 매우 직관적입니다. 기존 타입에 새로운 기능을 추가하려면 'extension' 키워드를 사용하면 됩니다. 제네릭 타입을 확장할 때에도 동일한 방법을 사용합니다.
예를 들어, 앞서 언급한 제네릭 스택 구조체를 확장하여 스택이 비어있는지 확인하는 메서드를 추가해볼 수 있습니다.
extension Stack {
func isEmpty() -> Bool {
return items.isEmpty
}
}
위의 확장은 Stack 구조체에 isEmpty라는 새로운 메서드를 추가하며, 이 메서드는 스택이 비어있는지를 판단하여 Bool 값을 반환합니다. 이처럼 확장을 사용하면 기존 타입에 새로운 메서드, 계산 속성, 이니셜라이저 등을 추가할 수 있습니다.
제네릭 타입을 확장할 때도 타입 매개변수 이름은 원래 타입에서 정의한 이름과 동일하게 사용해야 합니다. 예를 들어, Stack<Element>를 확장할 때 타입 매개변수의 이름은 Element로 지정해야 합니다.
그리고 제네릭 타입을 확장하여 새로운 메서드를 추가할 때, 그 메서드에서 타입 매개변수를 사용할 수 있습니다. 이를 통해 제네릭 타입의 기능을 유연하게 확장할 수 있습니다.
이번 포스팅에서는 제네릭에 대해서 정리를 해보았는데 아직 타입 제약(Type Constraints)이나 연관 타입(Associated Type)에 대해서 정리할 것이 남았지만 너무 길어질 것 같아서 다음 포스팅에서 다루도록 하겠습니다 :)
감사합니다!
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
[Swift] Combine 개념과 사용방법 이해하기 (0) | 2024.01.17 |
---|---|
[Swift] Generic과 Type 이해하기 (2) | 2024.01.09 |
[Swift] 예외처리 - 만약 에러가 발생한다면?? (3) | 2024.01.03 |
[Swift] Dictionary 완벽 이해하기 (2) | 2024.01.02 |
[Swift] Self 프로퍼티 (2) | 2023.10.30 |