제네릭(Generic)
💡 제네릭(Generic)
- 제네릭이란 타입에 의존하지 않는 범용 코드를 작성할 때 사용
- 코드의 중복을 피하고, 유연하게 작성할 수 있음
- 실제 타입 이름을 써주는 대신에 placeholder
를 사용
→ placeholder
는 타입의 종류를 알려주지 않지만 어떤 타입이라는 것은 알려줌
→ placeholder
의 실제 타입은 함수가 호출되는 순간 결정
→ placeholder
는 타입 매개변수로 쓰일 수도 있음, 이 타입 매개변수는 함수를 호출할 때마다 실제 타입으로 치환됨
- 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있음
- Array
와 Dictoinary
도 제네릭 타입
제네릭 함수(Generic Function)
- 타입에 제한 두지 않고 사용하고 싶을 때 사용
예시
// Code(01)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tempA = a
a = b
b = tempA
}
Code(01)에 경우 파라미터 모두 Int형일 경우엔 문제 없이 돌아가나,
swift는 민감한 언어이기 때문에 만약 파라미터 타입이 Double
, String
일 경우 사용할 수 없음
만약 Double
, String
에 대해 swap
함수를 사용하고 싶다면,
// Code(02)
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let tempA = a
a = b
b = tempA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let tempA = a
a = b
b = tempA
}
Code(02) 처럼 하나하나 형식에 맞게 오버로딩 해야 사용할 수 있음 근데 귀찮잖아...
이럴 때 쓰는 함수가 제네릭 함수
결론은,
// Code(03)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let tempA = a
a = b
b = tempA
}
앞서 말했듯 Double
, String
에 대한 swap
함수 사용에 경우 Code(03) 처럼 제네릭 함수를 사용해야 하며,
사용 시 Code(04)와 같이 Type Parameter
인 T
의 타입이 결정됨
// Code(04)
var someInt = 1
var aotherInt = 2
swapTwoValues(&someInt, &aotherInt) // 함수 호출 시 T는 Int 타입으로 결정됨
var someString = "Hi"
var aotherString = "Bye"
swapTwoValues(&someString, &aotherString) // 함수 호출 시 T는 String
제네릭 사용법
func 사용할_함수_이름<T>()
※ 간단하게 말하면 이런 식으로 정의할 수 있음
여기서 T
란?
- "Type Parameter"는 일종의 타입 매개변수
- 제네릭에서 실제 타입으로 대체될 수 있는 임의의 타입을 나타냄
- 일반적으로 제네릭 함수나 제네릭 데이터 구조를 작성할 때 사용
- T
는 새로운 형식이 아닌,
함수나 데이터 구조가 호출될 때 실제로 대체되는 placeholder
라는 것을 나타냄
제네릭 타입(Generic Type)
- 특정한 타입이 아닌, 여러 다른 타입을 수용할 수 있는 타입
- 함수, 메서드, 클래스, 구조체, 열거형 등을 정의할 수 있음
예시
스택(Stack)이라는 자료구조를 제네릭으로 구현한다면?
struct Stack<T> {
let items: [T] = []
mutating func push(_ item: T) { ... }
mutating func pop() -> T { ... }
}
제네릭 타입의 인스턴스를 생성
let stack1: Stack<Int> = .init()
let stack2 = Stack<Int>.init()
※ 제네릭을 사용하지 않는다면 스택은 특정 타입(예: Int
, String
)의 요소만을 저장할 수 있을 것
그러나, 제네릭을 사용하여 스택을 구현한다면 Int
타입 뿐만 아니라 어떤 타입이든 요소로 저장할 수 있게 됨
💡 타입 제약(Type Constraints)
- 제네릭 타입이나 함수가 특정 조건을 충족하는 타입만을 수용하도록 하는 기능
- 타입 제약은 코드의 안전성을 높이고 의도하지 않은 타입이 사용되는 것을 방지하기 위해 사용
→ 이를 통해 제네릭 코드를 보다 정교하게 제어하고, 보다 안전하게 사용할 수 있음
- 일반적으로 제네릭 코드에서는 어떤 타입이든 받아들일 수 있으나,
만약 특정 타입에 대해 제한을 두고 싶을 때가 있을 경우
→ 이때 타입 제약을 사용하여 특정 타입의 조건을 명시할 수 있음
타입 제약(Type Constraints) 활용
특정 프로토콜을 따르는 타입만 허용
- 제네릭 타입이나 함수가 특정 프로토콜을 준수하는 타입만을 수용하도록 할 수 있음
클래스 타입으로 제한
제네릭 타입이나 함수가 클래스 타입만을 수용하도록 할 수 있음
→ 이는 상속이 가능한 타입에 대해서만 제네릭을 적용할 수 있도록 함
특정 타입으로 제한
제네릭 타입이나 함수가 특정한 클래스나 구조체 타입을 받아들일 수 있도록 할 수 있음
프로토콜 제약(Protocol Constraints)
- 제네릭 타입이나 함수가 특정 프로토콜을 준수하는 타입만을 수용하도록 하는 것을 의미
→ 이를 통해 제네릭 코드가 특정한 프로토콜을 따르는 타입에 대해서만 동작하도록 제한할 수 있음
프로토콜 제약
타입 안전성 보장
- 프로토콜 제약을 사용하여 특정 프로토콜을 따르는 타입만을 받아들이도록 함으로써, 코드의 안전성을 보장할 수 있음
코드 재사용성
- 특정 프로토콜을 따르는 다양한 타입에 대해 동작하는 제네릭 코드를 작성할 수 있으므로, 코드의 재사용성이 높아짐
예시
func someFunction<T: SomeProtocol>(someParameter: T) {
// ...
}
T: SomeProtocol
- 제네릭 함수
someFunction
이 제네릭 타입 T를 사용할 때 SomeProtocol
프로토콜을 준수하는 타입에 제한을 둔다는 것을 나타냄- 따라서
someParameter
매개변수로 전달되는 값은SomeProtocol
프로토콜을 준수하는 타입이어야 함
제네릭 확장(extension)
- 기존의 구조체, 클래스, 열거형 등에 대한 확장에서 타입 매개변수를 사용할 수 있음
→ 이를 통해 동일한 기능을 다양한 타입에 대해 적용할 수 있음
예시
※ Array에 대한 확장 정의
printElements() : 배열의 요소를 출력하는 기능
extension Array {
func printElements() {
for element in self {
print(element)
}
}
}
// 사용 예시
let numbers = [1, 2, 3, 4, 5]
let strings = ["apple", "banana", "orange"]
numbers.printElements() // 출력: 1 2 3 4 5
strings.printElements() // 출력: apple banana orange
제네릭 함수와 오버로딩
- 특정 타입에 대해 다르게 동작하도록 제네릭 함수를 오버로딩할 수 있음
→ 이를 통해 제네릭 함수와 다른 함수 간에 우선순위를 정할 수 있고, 특정 타입에 대한 특별한 처리를 할 수 있음
→ 따라서 필요에 따라 제네릭 함수를 오버로딩하여 원하는 동작을 구현할 수 있음
func swapValues<T>(_ a: inout T, _ b: inout T) {
print("generic func")
let tempA = a
a = b
b = tempA
}
func swapValues(_ a: inout Int, _ b: inout Int) {
print("specialized func")
let tempA = a
a = b
b = tempA
}
위 코드경우 타입이 지정된 함수가 제네릭 함수보다 우선순위가 높음
그렇기 때문에,
var a = 1
var b = 2
swapValues(&a, &b) //"specialized func"
var c = "Hi"
var d = "Sodeul!"
swapValues(&c, &d) //"generic func"
Int
타입으로 swapValue
를 실행할 경우 : 타입이 지정된 함수가 실행
String
타입으로 swapValue
를 실행할 경우 : 제네릭 함수가 실행
' 𝗔𝗣𝗣𝗟𝗘 > SWIFT : GRAMMAR' 카테고리의 다른 글
Swift 기초 문법 - allSatisfy() (0) | 2024.03.30 |
---|---|
Swift 기초 문법 - 타입 메서드(Type Method) in 스태틱(Static) (2) | 2024.03.27 |
Swift 기초 문법 - 확장(Extension) (0) | 2024.03.14 |
Swift 기초 문법 - 프로토콜(Protocol) (0) | 2024.03.14 |
Swift 기초 문법 - ARC와 메모리 누수(Automatic Reference Counting) (0) | 2024.03.14 |