안녕하세요! 피피아노입니다 🎵
이번 포스팅은 지난번 포스팅에 이어서 제네릭(Generic)과 타입(Type)에 대해서 정리를 해보겠습니다!
지난 포스팅이 궁금하시다면 여기를 참고해주세요!
그럼 바로 시작해보겠습니다.
타입 제약(Type Constraints)
제네릭 기능의 타입 매개변수는 실제 사용시 타입의 제약 없이 사용할 수 있지만 종종 제네릭 함수가 처리해야 할 기능이 특정 타입에 한정되어야만 처리가 가능하거나, 제네릭 타입을 특정 프로토콜을 따르는 타입만 사용할 수 있도록 제약을 두어야 하는 상황이 발생합니다.
타입 제약은 이렇게 제약이 필요할 때 타입 매개변수가 가져야 할 제약을 지정할 수 있는 방법입니다.
타입 제약은 클래스 타입 또는 프로토콜로만 줄 수 있습니다. 즉, 열거형, 구조체 등의 타입은 타입 제약의 타입으로 사용할 수 없죠!
제네릭 타입 제약을 사용하면 특정 클래스의 하위 클래스만 가능하도록 제약을 둘 수 있습니다. 예를 들어 <T: UIViewController>와 같은 형태로 사용하면, T는 UIViewController의 하위 클래스만 가능합니다.
또한, 여러 프로토콜을 동시에 준수해야 하는 제약도 가능합니다. 이 경우에는 '&' 연산자를 사용해서 프로토콜을 연결해줍니다. 예를 들어, <T: SomeProtocol & AnotherProtocol>와 같이 사용하면 됩니다.
마지막으로, 'where' 절을 사용하면 더 복잡한 조건을 붙일 수 있습니다. 예를 들어,
<T, U where T: Sequence, U: Sequence, T.Iterator.Element == U.Iterator.Element>
와 같이 사용할 수 있습니다.
이는 T와 U가 모두 Sequence 프로토콜을 준수하고, 또한 T의 Iterator.Element와 U의 Iterator.Element이 같아야 한다는 제약을 뜻합니다. 이런 식으로 타입 제약을 통해 더 안전하고 유연한 코드를 작성할 수 있습니다.
프로토콜의 연관 타입
프로토콜을 정의할 때 연관 타입(Associated Type)을 함께 정의하면 유용하게 사용할 수 있습니다.
연관 타입은 프로토콜에서 사용할 수 있는 플레이스홀더 이름으로, 프로토콜에서 사용되는 타입에 대한 추상적인 이름을 제공하는 방법입니다. 이는 제네릭 함수나 제네릭 타입에서 타입 매개변수를 사용하는 것과 비슷한 역할을 합니다. 그러나 연관 타입은 프로토콜의 일부로서, 프로토콜을 구현하는 타입이 결정하는 이름입니다.
이를 통해 프로토콜을 사용하는 타입이 실제로 어떤 타입을 사용할지 결정하게 되며, 이를 통해 프로토콜 설계를 보다 유연하고 재사용 가능하게 만들 수 있습니다. 즉, 연관 타입은 프로토콜이 특정 작업을 수행하는데 필요한 타입을 '연관'시키는 방법을 제공합니다.
예를 들어, 'Container'라는 프로토콜이 있다고 가정해 보겠습니다. 이 프로토콜은 'Item'이라는 연관 타입을 가지고 있을 수 있습니다. 이 'Item'은 프로토콜을 구현하는 각 타입(예: 배열, 집합 등)이 결정하게 됩니다. 이렇게 연관 타입을 사용함으로써, 'Container' 프로토콜은 다양한 타입의 컨테이너에 대해 동일한 인터페이스를 제공할 수 있습니다.
제네릭 서브스크립트
서브스크립트도 제네릭을 활용하면 타입에 큰 제한 없이 구현할 수 있습니다.
제네릭 서브스크립트는 서브스크립트의 매개변수 타입과 반환 타입을 유연하게 만들어주는 기능입니다. 즉, 서브스크립트 내부에서 어떤 타입을 사용할 것인지 미리 정해두지 않고, 실제로 서브스크립트를 사용하는 시점에서 그 타입을 결정할 수 있게 해줍니다.
제네릭 서브스크립트의 사용 예를 살펴보도록 하겠습니다.
struct GenericDictionary<Key: Hashable, Value> {
private var data: [Key: Value]
init() {
data = [Key: Value]()
}
subscript<T>(key: Key) -> T? {
get {
return data[key] as? T
}
set {
data[key] = newValue as? Value
}
}
}
var dictionary = GenericDictionary<String, Any>()
dictionary["age"] = 30
dictionary["name"] = "John Doe"
let age: Int? = dictionary["age"]
let name: String? = dictionary["name"]
이 코드를 살펴보면 GenericDictionary는 Key와 Value라는 두 가지 타입에 대한 제네릭을 사용하고 있습니다. 이 구조체에는 제네릭 서브스크립트인 subscript<T>(key: Key) -> T?가 정의되어 있습니다. 이 서브스크립트는 Key 타입의 키를 받아서 T 타입의 값을 반환합니다.
dictionary["age"] = 30와 같이 서브스크립트를 사용하여 값을 저장할 때, 실제로 저장되는 값의 타입(Value)은 제네릭을 통해 결정됩니다. 이후 let age: Int? = dictionary["age"]와 같이 값을 꺼낼 때는 제네릭 서브스크립트를 통해 반환되는 값의 타입이 결정됩니다.
이렇게 제네릭 서브스크립트를 사용하면 서브스크립트의 동작을 유연하게 만들 수 있습니다.
이번 포스팅에서는 저번 포스팅에 이어서 제네릭과 타입에 대해서 정리를 해보았습니다.
추가로 더 공부해서 알게 되는 내용이 있다면 바로 포스팅 하도록 하겠습니다 :)
감사합니다!
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
[Swift] Combine의 Operator 알아보기 (0) | 2024.01.28 |
---|---|
[Swift] Combine 개념과 사용방법 이해하기 (0) | 2024.01.17 |
[Swift] Generic 이해하기 (0) | 2024.01.04 |
[Swift] 예외처리 - 만약 에러가 발생한다면?? (2) | 2024.01.03 |
[Swift] Dictionary 완벽 이해하기 (2) | 2024.01.02 |