제네릭이란?
제네릭은 프로그래밍 언어의 다양한 타입에 대응하여 유연 코드를 만들어주는 친구입니다.
- 타입의 약자인T를 파라메터 이름으로 사용하지만 다른 이름도 사용 가능해요.
- 타입 파라미터를 2개이상을 선언하는 것도 가능합니다
- 구조체, 클래스, 열거형에서도 제네릭이 사용 가능해요.
제네릭의 특징
제네릭의 가장 큰 특징은 바로 '재사용성'과 '타입 안전성'입니다.
- 재사용성: 같은 로직의 코드를 다양한 타입에 대해 재사용할 수 있게 해줍니다.
- 타입 안전성: 제네릭을 사용하면 컴파일러가 타입 체크를 할 수 있으므로, 타입 관련 오류를 줄이고 코드의 안전성을 높일 수 있습니다.
좀 딱딱하죠? 바로 스펀지밥 예시로 가시죠!
징징이는 집게리아의 캐셔를 맡고 있어요.
현금은 보통 0단위로 떨어지니까 int, 카드는 소숫점 계산이 가능하죠? 마지막으로 외상결제를 String으로 표현해봤습니다.
class 결제 {
private var 현금수단: Int?
private var 외상수단: String?
private var 카드수단: Double?
init(현금: Int? = nil, 외상: String? = nil, 카드: Double? = nil) {
self.현금수단 = 현금
self.외상수단 = 외상
self.카드수단 = 카드
}
func 현금결제하기() {
if let 현금 = 현금수단 {
print("결제수단: \(현금)원")
}
}
func 카드결제하기() {
if let 카드 = 카드수단 {
print("결제수단: \(카드)원")
}
}
func 외상결제하기() {
if let 외상 = 외상수단 {
print("결제수단: \(외상)")
}
}
}
이렇게 클래스 내부에 동일한 기능의 이름만 다른 메서드를 작성하게 되는데요.
제네릭을 사용한다면??
class 결제<T> {
private var 수단: T
init(수단: T) {
self.수단 = 수단
}
func 결제하기() {
if let amount = 수단 as? Int {
print("결제수단: \(amount)원")
} else if let amount = 수단 as? Double {
print("결제수단: \(amount)원")
} else {
print("안사요~")
}
}
}
let 현금결제 = 결제<Int>(수단: 10000)
현금결제.결제하기() // "결제수단: 10000원" 출력
let 카드결제 = 결제<Double>(수단: 100.50)
카드결제.결제하기() // "결제수단: 100.5원" 출력
let 외상 = 결제<String>(수단: "(빵을 주며)집게사장님 사랑해요!!")
외상.결제하기() // "안사요~" 출력
요렇게 중복코드 제거가 가능하답니다 ㅎㅎ Int, String, Double 등 다양한 타입을 한번에 처리할 수 있으면서 타입 안전성도 보장해주죠.
제네릭의 장단점
위에서 언급한 재사용성과 타입 안전성을 장점으로 가지고 있지만 단점 또한 존재하는데요,
- 복잡성: 제네릭을 사용하면 코드가 복잡해질 수 있습니다. 특히 다양한 타입에 대응해야 하는 경우에는 코드의 가독성을 떨어트릴 수 있어요.
제네릭이 주로 사용되는 경우
제네릭은 주로 컬렉션 타입이나, 다른 타입에 의존하지 않는 함수나 메소드를 작성할 때 사용해요.
func exchange<T, U>(a: inout T, b: inout U) {
let temp = a
a = b as! T
b = temp as! U
}
var krabbyPatty = "Krabby Patty"
var price = 1.99
exchange(a: &krabbyPatty, b: &price)
위 예제는 타입이 다른 두 개의 항목을 교환하는 함수인데요, 원래는 제네릭을 사용하지 않으면 컴파일 에러를 일으키게 되죠. 하지만 제네릭을 사용해서 이렇게 간편하게 사용이 가능합니다 ㅎㅎ
inout은 또 뭐죠...? ㅇㅇa..
inout 키워드는 Swift에서 함수의 매개변수를 참조로 전달하기 위해 사용하는 키워드인데요,
기본적으로 Swift에서는 함수의 매개변수로 값을 전달할 때 실제 값이 복사되어 전달되는 Call by Value 방식을 사용합니다. 그래서 함수 내부에서 매개변수의 값을 변경해도, 그 변경이 함수 외부의 원본 데이터에는 영향을 미치지 않죠.
하지만 inout 키워드를 사용하면 참조에 의한 전달(Call by Reference) 방식을 사용할 수 있습니다. 이 방식을 사용하면 함수의 매개변수로 값을 전달할 때 값의 메모리 주소가 전달되며, 따라서 함수 내부에서 매개변수의 값을 변경하면 그 변경이 함수 외부의 원본 데이터에도 영향을 미쳐요.
한줄 요약
제네릭은 필요에 따라 다양한 타입을 받아 코드를 유연하게 작성할 수 있게 해주는 도구라고 할 수 있어요.
조금 더 딥하게 파면 성능적 이점도 있는데 이 부분은 다른 포스팅에서 따로 다뤄보도록 할게요 :)
레퍼런스
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
https://zeddios.tistory.com/226
'스폰지밥으로 공부하는 swift > swift 문법' 카테고리의 다른 글
CodeBaseUI에서 UI컴포넌트를 선언하는 다양한 방법들 (0) | 2024.01.31 |
---|---|
클로저 왜 씀? (0) | 2024.01.29 |
[값타입 vs 참조타입]을 '집게리아 레시피'로 이해 해보좌 (1) | 2024.01.28 |
final 왜 씀..? (0) | 2024.01.06 |
unowned vs weak 뭐가 다른데? (1) | 2024.01.04 |