Rx를 들어가기 전! 제네릭에 대해서 복습 겸 한 번 정리해 보자고!
제네릭.. 이름부터 간지 철철❗️
애플 말에 따르면 Swift에서 가장 강력한 기능 중 하나가 바로 제네릭인데,
이유는 바로! Swift 표준 라이브러리의 대다수는 제네릭으로 선언되어 있기 때문임.
제네릭의 기본개념
제네릭은 코드의 중복을 줄이고, 타입에 안전한 코드를 작성할 수 있도록 도와줌.
예를 들어, 두 값을 서로 바꾸는 함수를 비교해 보자.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
평소에 우리가 제네릭을 쓰지 않는다면 이런 식으로 짤 것임.
하지만, 요 func는 Int밖에 들어가지 않지?
그럼, Double, String, 등등 다른 타입이 올 경우엔 어떡하지?
제네릭이 없다면 하나하나 파라미터 값을 변경하여 메서드를 생성해 주어야 함..
하지만! 우리에겐 제네릭이 있지. 후후
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
요렇게! 타입에 제한을 두지 않고 쓸 수 있다!
여기서 <T>는 너 T야!? 할 때 그 T가 아니라,
타입 파라미터(Type parameter)라고 부름.
타입 파라미터는 임의의 타입을 지정하고 이름을 지정하여 꺾쇠괄호 사이에 기록하고,
함수의 이름 바로 뒤에 작성함
아무튼, 이렇게 swapTwoValues라는 제네릭으로 선언해 주면,
var testNumA = 1
var tsetNumB = 2
swapTwoValues(&testnumA, &testnumB) // int type
var testStringA = "Hi"
var testStringB = "Hello"
swapTwoValues(&testStringA, &testStringB) // string type
이렇게 실제 함수를 호출할 때
제네릭의 타입 파라미터 <T>의 값이 정해지는 거고,
만약 서로 다른 타입을 전달하게 되면,
swapTwoValues(&testNumA, &testStringA)
Cannot convert value of type 'String' to expected argument type 'Int'
요런 에러가 뜨게 되는 거지.
제네릭 타입
다음으로 위에서 배운 개념을 이용한 함수를 제네릭 함수 라고 하는데,
이 제네릭은 함수뿐만 아니라 구조체, 클래스, 열거형 타입에도 선언이 가능함.
이를 제네릭 타입이라고 한다.
첫 번째로 제네릭 구조체에 대해서 알아보자.
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
}
이렇게 제네릭 타입으로 스택을 선언할 수 있다.
++++++
여기에서, mutating func가 뭘까?
struct or enumeration 내에서 인스턴스 메서드가 해당 인스턴스의 프로퍼티 수정을
가능하게 만들어 줌.
그러면 왜 'mutating' 키워드를 쓰는 건데?
구조체와 열거형은 값 타입임. 기본적으로 값 타입의 인스턴스 메서드는
인스턴스 프로퍼티를 변경할 수 없음.
하지만, mutating을 사용하면 메서드 내에서 인스턴스의 프로퍼티 변경이 가능해짐!
다음으로, 제네릭 클래스에 대해서 알아보자.
public class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
요건 자바코드긴 한데,
예시로 보여주기 너무 좋아서 가지고 와봄.
이런 식으로 'Box'클래스는 'T'라는 제네릭 타입 매개변수를 가지고,
이렇게 박스가 다양한 타입의 아이템을 가질 수 있도록 선언이 가능함.
마지막으로, 제네릭 열거형 타입임
enum Result<Value> {
case success(Value)
case failure(Error)
}
이렇게 Value라는 제네릭 타입 매개변수를 쥐어주면, 성공 케이스가
다양한 타입의 값을 가질 수 있어짐
제네릭 타입 제약
이번에 제네릭을 찾아보면서 좀 중요할 것 같은데? 싶은 부분이었음.
(어렵다 -> 중요하다) ㅋㅋ
이제, 제네릭을 사용할 때 타입제약을 추가하여 안전하고 의도에 맞는
타입을 사용할 수 있도록 제약을 두는 것임!
구조체에서의 타입제약
struct Stack<Element: Equatable> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
func contains(_ item: Element) -> Bool {
return items.contains(item)
}
}
보면 'Element'옆에 'Equatable'이라는 것이 추가되었음
이게 뭐냐 하면 예를 들어, 우리가 파라미터로 두 개의 값을 받아서
두 값이 같으면 ture, 아니면 false를 반환하는 함수를 제네릭으로 선언하려고 할 때 어떻게 할 수 있을까?
func isSameValues<T>(_ a: T, _ b: T) -> Bool {
return a == b
}
이런 식으로 쓰겠지?
하지만 이렇게 쓰면 다음과 같은 오류가 발생함.
Binary operator '==' cannot be applied to two 'T' operands
왜냐하면, == 이란 연산자는 a와 b의 타입이 Equatable이란 프로토콜을 준수할 때만 사용이 가능하기 때문임.
그렇기 때문에? <Element: Equatable> 이런식으로 제약을 주는 것이다!
클래스에서의 타입제약
class Animal { }
class Cat { }
class Dog: Animal { }
func animalShelter<T: Animal>(_ a: T) { }
이렇게 T: Animal처럼 클래스 이름을 생성한 뒤에
let animal = Animal.init()
let dog = Dog.init()
let cat = Cat.init()
animalShelter(animal)
animalShelter(dog)
animalShelter(cat)
요런 식으로 호출하면, Animal클래스 인스턴스인 animal과 Dog는
animalShelter이란 제네릭 함수를 실행시킬 수 있지만,
cat은 불가능해짐.
마지막으로 열거형에 타입제약은
enum Result<Value: Decodable> {
case success(Value)
case failure(Error)
}
이런 식으로 Value가 Decodable 프로토콜을 준수하는 타입이어야 한다 라는
제약을 걸 수도 있음
이렇게 제약조건까지 알아봤슴미당!
다음에 봐요~
'Swift' 카테고리의 다른 글
Swift RxSwift (0) | 2024.08.01 |
---|---|
Xcode Instruments (0) | 2024.07.31 |
Swift MVVM (0) | 2024.07.19 |
Swift 싱글톤 패턴 (0) | 2024.07.14 |
(CS) 네트워크 대비 지식쌓기 (2/2) (0) | 2024.07.12 |