Generic
정말 많은 언어에서 후반부에 나오는 개념이다. 타입과 관련해서 간편한 기능이라고 생각만 하고 있었는데, Swift 강좌를 보면서 다시 등장한 개념이라 이번 기회에 정리해보고자 한다.
우선 애플 공식 문서에서 Generic에 대한 설명을 참고해보자.
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
Documentation
docs.swift.org
Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
- 간단히 요약하자면, 어떠한 타입과 관련해서 유연하게, 함수와 타입을 재사용할 수 있다. 추상화, 명확한 코드를 작성할 수 있다… 정도로 해석할 수 있다.
이외에도 적힌 문서를 참고하면 Swift에서 Generic은 정말 강력한 특징을 지니고 있고, 대부분의 standard library들이 generic code로 작성되어있다고 한다. 예로 Array, Dictionary 컬렉션 타입들이 있다고 한다. 나중에 이에 대해 추가적으로 살펴보자.
그래서 Generic이 해결할 수 있는 게 뭐가 있는데?
다음과 같은 코드를 보자(공식 문서 예제 참고)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
Int 타입 변수들에 대한 값을 swap 해주는 함수이다.
그렇다면 Double, Character … 등 다른 타입에 대해 swap 해주는 기능들은..?
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}

타입마다 함수들을 다시 새로운 파라미터 타입과 함께 정의해야 한다.
하지만 Generic Functions로 이를 적용시켜 보자면
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var a: Int = 3
var b: Int = 4
swapTwoValues(a, b)
print(a) // 4
print(b) // 3
var c: String = "Hello"
var d: String = "Swift"
swapTwoValues(c, d)
print(c) // Swift
print(d) // Hello

위와 같이 범용성 있게 사용이 가능하다.
문법적인 측면에서 살펴보자
- 함수의 제네릭 버전은 Int, String … 와 같은 실제 타입 이름 대신에 “T”라는 임의의 타입 이름을 사용한다.
- 이 임의의 타입 이름은 T 가 무엇이어야 하는지 아무 말도 하지 않지만
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
- 이와 같이 코드가 작성된 경우 a, b는 모두 같은 타입 T 여야 한다.
- 그리고 T의 실제 타입은 swapTwoValues(_: _:) 함수가 호출될 때마다 결정된다.
그렇다면 제네릭 함수와 제네릭이 아닌 함수의 차이점은 뭘까?
→ 제네릭 함수의 이름에 바로 임의의 타입 이름(T)이 꺾쇠 괄호 내 에 위치한다는 거다.
지금까지 계속해서 함수에 관련지어서만 이야기를 하는데, 다른 곳에서도 사용가능한가?
→ 제네릭은 타입에 관계없이, 하나의 정의(구현)로 모든 타입(자료형)을 처리할 수 있는 문법이다.
- 그렇다면… 함수뿐만 아니라 제네릭 구조체/클래스/열거형도 가능하다는 것
저 <T>는 뭐를 의미하는 건가?
- 타입 파라미터로 실제 자료형으로 대체되는 플레이스 홀더(어떤 기호같은 것)를 의미한다. 새로운 형식이 생성되는 것이 아닌 것에 주의하자.
- 코드가 실행될때 문맥에 따라서 실제 형식으로 대체되는 “플레이스 홀더”일 뿐 그 이상 그 이하도 아니다!
제네릭 함수, 클래스, 구조체, 열거형
이전 이야기한 함수, 클래스, 구조체, 열거형에서의 제네릭 문법 사용예시를 보자
[제네릭 함수]
func printArray<T>(array: [T]) { // <T>는 플레이스 홀더일뿐!
for element in array {
print(element)
}
}
printArray(array: numbers) // 플레이스홀더 ====> [Int]
printArray(array: scores) // 플레이스홀더 ====> [Double]
printArray(array: people) // 플레이스홀더 ====> [String]
- <T> 는 플레이스 홀더의 기능을 하며
- [T] 에서의 T는 타입 파라미터의 사용으로 본래 타입의 사용하는 위치(파라미터, 바디, 리턴형)에서 타입이 필요한 곳에 타입 파라미터로 사용된다. 이는 실제 함수 호출시에 실제 타입으로 치환되는 것을 유의하자!
[제네릭 클래스]
class SomeColor<T> {
var red: T
var green: T
var blue: T
...
}
[제네릭 타입의 확장(1)]
extension SomeColor {
func getColor() -> T {
...
}
}
- 타입 파라미터 명시없이 확장. SomeColor (X)
- 본체의 제네릭에서 정의한 타입 ㅏ라미터 사용 가능
[제네릭 열거형]
enum Pet<T> {
case dog
case cat
case etc(T)
}
let animal = Pet.etc("고슴도치")
let animal2= Pet.etc(30)
- 열거형에서 연관값을 가질때 제네릭으로 정의가능하다.
- 어짜피 케이스는 자체가 선택항목 중에 하나일뿐(특별타입)이고, 그것을 타입으로 정의할 일은 없다.
[제네릭 구조체의 확장]
- 애플 공식문서의 예제를 가져와봤다.
- 아래의 설명에서도 계속 언급되니 자세히 보자.
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
// **[제네릭 타입의 확장(2)]**
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
[확장에서의 제약]
- where절을 통해 확장 또한 제약을 줄 수 있다.
- 아래 코드는 타입 파라미터 Element가 FixedWidthInteger라는 프로토콜을 준수해야 한다는 것.
extension Stack where Element: FixedWidthInteger {
mutating func pop() -> Element { return self.removeLast() }
}
let nums = [1, 2, 3]
let strs = ["a", "b", "c"]
nums.pop() // O
strs.pop() // X
- 물론 상황별로 줄 수도 있다.
extension Stack {
func average() -> Double where ... {
...
}
func endsWith(... ) -> Bool where ... }
...
}
}
제네릭의 타입 제약
- 제네릭에서 타입을 제약할 수 있다. 즉, 모든 타입이 다 가능하지 않도록 할 수 있다.
func doSomething<T: 제약조건>(a: T) {
...
}
// func doSomething<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
...
}
- 위와 같은 코드가 있을때 “프로토콜 제약”, “클래스 타입 제약”을 생성할 수 있다.
- 이는 각각 특정 프로토콜을 따르는 타입만 가능하도록, 특정 클래스와 상속관계 내에 속하는 클래스 타입만 가능하도록 제약하는 것이다.
아래의 코드를 보자.
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
→ 해당 코드는 컴파일이 되지 않는다.
→ value == valueToFind에서, Swift의 모든 타입이 동등 연산자( == )로 비교할 수 잇는 것은 아니기 때문이다.
- 위의 문제는 “동등 연산자, 비동등 연산자(≠)”를 구현하기 위해 이를 준수하는 타입을 요구하는 Equatable 이라는 프로토콜을 제약으로 해주어야 한다.
- 이를 반영한 (Equatable 프로토콜을 준수하는 T타입의 모든 것) 코드는 아래와 같다.
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
그러면 “클래스 제약”은 어떻게 사용하나요?
- 아래의 코드를 보자.
- 특정 클래스와 상속관계 내에 있는 클래스만 타입으로 사용할 수 있다는 제약을 거는 것이고, 당연히 상속관계가 없는 구조체, 열거형은 사용하지 못한다.
class Person {}
class Student: Person {}
let person = Person()
let student = Student()
func personClassOnly<T: Person>(array: [T]) {
// 함수의 내용 정의
}
personClassOnly(array: [person, person])
personClassOnly(array: [student, student])
제네릭 구체/특정화(specialization) 함수 구현
- 항상 제네릭을 적용시킨 함수를 실행하게만 하면, 또다른 불편함이 생기지 않을까?
- 제네릭 함수와 동일한 함수이름에 구체적인 타입을 명시하면, 해당 구체적인 타입의 함수가 실행된다.
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
// 문자열의 경우 대소문자 무시하고 글자 비교
func findIndex(item: String, array: [String]) -> Int? {
let lowercasedItem = item.lowercased()
for (index, value) in array.enumerated() {
if value.lowercased() == lowercasedItem {
return index
}
}
return nil
}
let aString = "jobs"
let someStringArray = ["Jobs", "Musk"]
if let index2 = findIndex(item: aString, array: someStringArray) {
print("문자열의 비교:", index2)
} else {
print("일치하는 항목을 찾을 수 없습니다.")
}
// 문자열의 비교: 0
프로토콜에서의 제네릭
- 프로토콜에서의 제네릭 타입(연관타입)의 사용은 어떻게 할까?
- 우선 간단하게 말하자면, 프로토콜에서는 연관타입(associatedtype)이라는 것을 사용해, 제네릭과 동일한 타입 파라미터를 지정한다.
- associatedtype(연관타입)이라는 키워드를 쓰고, 옆에 타입 파라미터로 사용할 문자를 작성한다.
- 여기서도 T는 “플레이스 홀더”의 역할이다.
protocol RemoteControl {
associatedtype T
func change(to: T)
func alert() -> T?
}
- 프로토콜의 채택 및 연관 타입의 사용은 아래와 같다.
class TV: RemoteControl {
typealias T = String // 생략 가능
func change(to: String) { ... }
func alert() -> String? { ... }
}

아니 왜 얘네는 혼자는 associatedtype을 적용하고 그러는 거여..
강의에서는 프로토콜은 타입들이 채택할 수 있는 한차원 높은 단꼐에서 요구사항만을 선언하는 개념이기 때문에 제네릭 타입과 조금 다른 개념(연관타입)을 추가적으로 도입한 것일 뿐이라고 한다.
- 그냥 → associatedtype T라고 명시한다고 알아두자.
- 연관 형식에 제약을 추가한다면 아래와 같이 명시할 수 있다.
- 3개의 예제를 보자.
protocol RemoteControl2 {
associatedtype Element: Equatable // <T: Equatable> 제약 조건을 추가
...
}
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
- 마지막 예시에 관한 제약들에 대해 자세히 살펴보면..
- 우선 함수의 2가지 타입 파라미터에 대한 요구사항은 아래와 같다.
-
더보기C1: Container 로 작성했듯이 C1 은 Container 프로토콜을 준수해야 합니다.
C2: Container 로 작성했듯이 C2 는 Container 프로토콜을 준수해야 합니다.
C1.Item == C2.Item 으로 작성했듯이 C1 에 대한 Item 은 C2 에 대한 Item 과 동일해야 합니다.
C1.Item: Equatable 로 작성했듯이 C1 에 대한 Item 은 Equatable 프로토콜을 준수해야 합니다.
-
- 첫번째 그리고 두번째 요구사항은 함수의 타입 파라미터 리스트에 정의되고 세번째 그리고 네번째 요구사항은 함수의 제너릭 where 절에 정의된다.
-
더보기someContainer 는 C1 타입의 컨테이너 입니다.
anotherContainer C2 타입의 컨테이너 입니다.
someContainer 와 anotherContainer 는 같은 타입의 항목을 포함합니다.
someContainer 안에 항목은 서로 다름을 확인하기 위해 비동등 연산자 (!=)를 사용하여 체크될 수 있습니다.
-
- 그리고 마지막으로..
-
더보기세번째와 네번째 요구사항은 someContainer 의 항목과 정확히 동일한 타입이므로 anotherContainer 의 항목도 != 연산자로 확인할 수 있음을 의미합니다.
이 요구사항을 통해 allItemsMatch(_:_:) 함수는 컨테이너 타입이 다른 경우에도 두 컨테이너를 비교할 수 있습니다.
-
- 우선 함수의 2가지 타입 파라미터에 대한 요구사항은 아래와 같다.
- 아 그리고 당연히 연관타입도 where절을 사용할 수 있다.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
추가
제네릭 서브 스크립트
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result: [Item] = []
for index in indices {
result.append(self[index])
}
return result
}
}
Container
프로토콜의 확장은 시퀀스의 인덱스를 가지고 각 주어진 인덱스의 항목을 포함한 배열을 반환하는 서브 스크립트를 추가합니다. 이 제너릭 서브 스크립트는 다음과 같이 제한됩니다:- 꺾쇠 괄호에 제너릭 파라미터
Indices
는 표준 라이브러리의Sequence
프로토콜을 준수하는 타입이어야 합니다. - 서브 스크립트는
Indices
타입의 인스턴스 인indices
라는 단일 파라미터를 가집니다. - 제너릭
where
절은 시퀀스에 대한 반복자는Int
타입의 요소여야 합니다. 이렇게 하면 시퀀스의 인덱스는 컨테이너에 사용되는 인덱스와 동일한 타입입니다.
- 꺾쇠 괄호에 제너릭 파라미터

참고
https://babbab2.tistory.com/136
chatgpt…
'iOS > Swift' 카테고리의 다른 글
[Swift] Date, Calendar, DateFormatter (1) | 2024.02.25 |
---|---|
Result Type에 대한 이해 (1) | 2024.02.19 |
비동기 프로그래밍(About Asynchronous) [2] (1) | 2024.02.17 |
비동기 프로그래밍(About Asynchronous) [1] (0) | 2024.02.16 |
Swift No.26 (0) | 2024.02.08 |
Generic
정말 많은 언어에서 후반부에 나오는 개념이다. 타입과 관련해서 간편한 기능이라고 생각만 하고 있었는데, Swift 강좌를 보면서 다시 등장한 개념이라 이번 기회에 정리해보고자 한다.
우선 애플 공식 문서에서 Generic에 대한 설명을 참고해보자.
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
Documentation
docs.swift.org
Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
- 간단히 요약하자면, 어떠한 타입과 관련해서 유연하게, 함수와 타입을 재사용할 수 있다. 추상화, 명확한 코드를 작성할 수 있다… 정도로 해석할 수 있다.
이외에도 적힌 문서를 참고하면 Swift에서 Generic은 정말 강력한 특징을 지니고 있고, 대부분의 standard library들이 generic code로 작성되어있다고 한다. 예로 Array, Dictionary 컬렉션 타입들이 있다고 한다. 나중에 이에 대해 추가적으로 살펴보자.
그래서 Generic이 해결할 수 있는 게 뭐가 있는데?
다음과 같은 코드를 보자(공식 문서 예제 참고)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
Int 타입 변수들에 대한 값을 swap 해주는 함수이다.
그렇다면 Double, Character … 등 다른 타입에 대해 swap 해주는 기능들은..?
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}

타입마다 함수들을 다시 새로운 파라미터 타입과 함께 정의해야 한다.
하지만 Generic Functions로 이를 적용시켜 보자면
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var a: Int = 3
var b: Int = 4
swapTwoValues(a, b)
print(a) // 4
print(b) // 3
var c: String = "Hello"
var d: String = "Swift"
swapTwoValues(c, d)
print(c) // Swift
print(d) // Hello

위와 같이 범용성 있게 사용이 가능하다.
문법적인 측면에서 살펴보자
- 함수의 제네릭 버전은 Int, String … 와 같은 실제 타입 이름 대신에 “T”라는 임의의 타입 이름을 사용한다.
- 이 임의의 타입 이름은 T 가 무엇이어야 하는지 아무 말도 하지 않지만
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
- 이와 같이 코드가 작성된 경우 a, b는 모두 같은 타입 T 여야 한다.
- 그리고 T의 실제 타입은 swapTwoValues(_: _:) 함수가 호출될 때마다 결정된다.
그렇다면 제네릭 함수와 제네릭이 아닌 함수의 차이점은 뭘까?
→ 제네릭 함수의 이름에 바로 임의의 타입 이름(T)이 꺾쇠 괄호 내 에 위치한다는 거다.
지금까지 계속해서 함수에 관련지어서만 이야기를 하는데, 다른 곳에서도 사용가능한가?
→ 제네릭은 타입에 관계없이, 하나의 정의(구현)로 모든 타입(자료형)을 처리할 수 있는 문법이다.
- 그렇다면… 함수뿐만 아니라 제네릭 구조체/클래스/열거형도 가능하다는 것
저 <T>는 뭐를 의미하는 건가?
- 타입 파라미터로 실제 자료형으로 대체되는 플레이스 홀더(어떤 기호같은 것)를 의미한다. 새로운 형식이 생성되는 것이 아닌 것에 주의하자.
- 코드가 실행될때 문맥에 따라서 실제 형식으로 대체되는 “플레이스 홀더”일 뿐 그 이상 그 이하도 아니다!
제네릭 함수, 클래스, 구조체, 열거형
이전 이야기한 함수, 클래스, 구조체, 열거형에서의 제네릭 문법 사용예시를 보자
[제네릭 함수]
func printArray<T>(array: [T]) { // <T>는 플레이스 홀더일뿐!
for element in array {
print(element)
}
}
printArray(array: numbers) // 플레이스홀더 ====> [Int]
printArray(array: scores) // 플레이스홀더 ====> [Double]
printArray(array: people) // 플레이스홀더 ====> [String]
- <T> 는 플레이스 홀더의 기능을 하며
- [T] 에서의 T는 타입 파라미터의 사용으로 본래 타입의 사용하는 위치(파라미터, 바디, 리턴형)에서 타입이 필요한 곳에 타입 파라미터로 사용된다. 이는 실제 함수 호출시에 실제 타입으로 치환되는 것을 유의하자!
[제네릭 클래스]
class SomeColor<T> {
var red: T
var green: T
var blue: T
...
}
[제네릭 타입의 확장(1)]
extension SomeColor {
func getColor() -> T {
...
}
}
- 타입 파라미터 명시없이 확장. SomeColor (X)
- 본체의 제네릭에서 정의한 타입 ㅏ라미터 사용 가능
[제네릭 열거형]
enum Pet<T> {
case dog
case cat
case etc(T)
}
let animal = Pet.etc("고슴도치")
let animal2= Pet.etc(30)
- 열거형에서 연관값을 가질때 제네릭으로 정의가능하다.
- 어짜피 케이스는 자체가 선택항목 중에 하나일뿐(특별타입)이고, 그것을 타입으로 정의할 일은 없다.
[제네릭 구조체의 확장]
- 애플 공식문서의 예제를 가져와봤다.
- 아래의 설명에서도 계속 언급되니 자세히 보자.
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
// **[제네릭 타입의 확장(2)]**
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
[확장에서의 제약]
- where절을 통해 확장 또한 제약을 줄 수 있다.
- 아래 코드는 타입 파라미터 Element가 FixedWidthInteger라는 프로토콜을 준수해야 한다는 것.
extension Stack where Element: FixedWidthInteger {
mutating func pop() -> Element { return self.removeLast() }
}
let nums = [1, 2, 3]
let strs = ["a", "b", "c"]
nums.pop() // O
strs.pop() // X
- 물론 상황별로 줄 수도 있다.
extension Stack {
func average() -> Double where ... {
...
}
func endsWith(... ) -> Bool where ... }
...
}
}
제네릭의 타입 제약
- 제네릭에서 타입을 제약할 수 있다. 즉, 모든 타입이 다 가능하지 않도록 할 수 있다.
func doSomething<T: 제약조건>(a: T) {
...
}
// func doSomething<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
...
}
- 위와 같은 코드가 있을때 “프로토콜 제약”, “클래스 타입 제약”을 생성할 수 있다.
- 이는 각각 특정 프로토콜을 따르는 타입만 가능하도록, 특정 클래스와 상속관계 내에 속하는 클래스 타입만 가능하도록 제약하는 것이다.
아래의 코드를 보자.
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
→ 해당 코드는 컴파일이 되지 않는다.
→ value == valueToFind에서, Swift의 모든 타입이 동등 연산자( == )로 비교할 수 잇는 것은 아니기 때문이다.
- 위의 문제는 “동등 연산자, 비동등 연산자(≠)”를 구현하기 위해 이를 준수하는 타입을 요구하는 Equatable 이라는 프로토콜을 제약으로 해주어야 한다.
- 이를 반영한 (Equatable 프로토콜을 준수하는 T타입의 모든 것) 코드는 아래와 같다.
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
그러면 “클래스 제약”은 어떻게 사용하나요?
- 아래의 코드를 보자.
- 특정 클래스와 상속관계 내에 있는 클래스만 타입으로 사용할 수 있다는 제약을 거는 것이고, 당연히 상속관계가 없는 구조체, 열거형은 사용하지 못한다.
class Person {}
class Student: Person {}
let person = Person()
let student = Student()
func personClassOnly<T: Person>(array: [T]) {
// 함수의 내용 정의
}
personClassOnly(array: [person, person])
personClassOnly(array: [student, student])
제네릭 구체/특정화(specialization) 함수 구현
- 항상 제네릭을 적용시킨 함수를 실행하게만 하면, 또다른 불편함이 생기지 않을까?
- 제네릭 함수와 동일한 함수이름에 구체적인 타입을 명시하면, 해당 구체적인 타입의 함수가 실행된다.
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
// 문자열의 경우 대소문자 무시하고 글자 비교
func findIndex(item: String, array: [String]) -> Int? {
let lowercasedItem = item.lowercased()
for (index, value) in array.enumerated() {
if value.lowercased() == lowercasedItem {
return index
}
}
return nil
}
let aString = "jobs"
let someStringArray = ["Jobs", "Musk"]
if let index2 = findIndex(item: aString, array: someStringArray) {
print("문자열의 비교:", index2)
} else {
print("일치하는 항목을 찾을 수 없습니다.")
}
// 문자열의 비교: 0
프로토콜에서의 제네릭
- 프로토콜에서의 제네릭 타입(연관타입)의 사용은 어떻게 할까?
- 우선 간단하게 말하자면, 프로토콜에서는 연관타입(associatedtype)이라는 것을 사용해, 제네릭과 동일한 타입 파라미터를 지정한다.
- associatedtype(연관타입)이라는 키워드를 쓰고, 옆에 타입 파라미터로 사용할 문자를 작성한다.
- 여기서도 T는 “플레이스 홀더”의 역할이다.
protocol RemoteControl {
associatedtype T
func change(to: T)
func alert() -> T?
}
- 프로토콜의 채택 및 연관 타입의 사용은 아래와 같다.
class TV: RemoteControl {
typealias T = String // 생략 가능
func change(to: String) { ... }
func alert() -> String? { ... }
}

아니 왜 얘네는 혼자는 associatedtype을 적용하고 그러는 거여..
강의에서는 프로토콜은 타입들이 채택할 수 있는 한차원 높은 단꼐에서 요구사항만을 선언하는 개념이기 때문에 제네릭 타입과 조금 다른 개념(연관타입)을 추가적으로 도입한 것일 뿐이라고 한다.
- 그냥 → associatedtype T라고 명시한다고 알아두자.
- 연관 형식에 제약을 추가한다면 아래와 같이 명시할 수 있다.
- 3개의 예제를 보자.
protocol RemoteControl2 {
associatedtype Element: Equatable // <T: Equatable> 제약 조건을 추가
...
}
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
- 마지막 예시에 관한 제약들에 대해 자세히 살펴보면..
- 우선 함수의 2가지 타입 파라미터에 대한 요구사항은 아래와 같다.
-
더보기C1: Container 로 작성했듯이 C1 은 Container 프로토콜을 준수해야 합니다.
C2: Container 로 작성했듯이 C2 는 Container 프로토콜을 준수해야 합니다.
C1.Item == C2.Item 으로 작성했듯이 C1 에 대한 Item 은 C2 에 대한 Item 과 동일해야 합니다.
C1.Item: Equatable 로 작성했듯이 C1 에 대한 Item 은 Equatable 프로토콜을 준수해야 합니다.
-
- 첫번째 그리고 두번째 요구사항은 함수의 타입 파라미터 리스트에 정의되고 세번째 그리고 네번째 요구사항은 함수의 제너릭 where 절에 정의된다.
-
더보기someContainer 는 C1 타입의 컨테이너 입니다.
anotherContainer C2 타입의 컨테이너 입니다.
someContainer 와 anotherContainer 는 같은 타입의 항목을 포함합니다.
someContainer 안에 항목은 서로 다름을 확인하기 위해 비동등 연산자 (!=)를 사용하여 체크될 수 있습니다.
-
- 그리고 마지막으로..
-
더보기세번째와 네번째 요구사항은 someContainer 의 항목과 정확히 동일한 타입이므로 anotherContainer 의 항목도 != 연산자로 확인할 수 있음을 의미합니다.
이 요구사항을 통해 allItemsMatch(_:_:) 함수는 컨테이너 타입이 다른 경우에도 두 컨테이너를 비교할 수 있습니다.
-
- 우선 함수의 2가지 타입 파라미터에 대한 요구사항은 아래와 같다.
- 아 그리고 당연히 연관타입도 where절을 사용할 수 있다.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
추가
제네릭 서브 스크립트
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result: [Item] = []
for index in indices {
result.append(self[index])
}
return result
}
}
Container
프로토콜의 확장은 시퀀스의 인덱스를 가지고 각 주어진 인덱스의 항목을 포함한 배열을 반환하는 서브 스크립트를 추가합니다. 이 제너릭 서브 스크립트는 다음과 같이 제한됩니다:- 꺾쇠 괄호에 제너릭 파라미터
Indices
는 표준 라이브러리의Sequence
프로토콜을 준수하는 타입이어야 합니다. - 서브 스크립트는
Indices
타입의 인스턴스 인indices
라는 단일 파라미터를 가집니다. - 제너릭
where
절은 시퀀스에 대한 반복자는Int
타입의 요소여야 합니다. 이렇게 하면 시퀀스의 인덱스는 컨테이너에 사용되는 인덱스와 동일한 타입입니다.
- 꺾쇠 괄호에 제너릭 파라미터

참고
https://babbab2.tistory.com/136
chatgpt…
'iOS > Swift' 카테고리의 다른 글
[Swift] Date, Calendar, DateFormatter (1) | 2024.02.25 |
---|---|
Result Type에 대한 이해 (1) | 2024.02.19 |
비동기 프로그래밍(About Asynchronous) [2] (1) | 2024.02.17 |
비동기 프로그래밍(About Asynchronous) [1] (0) | 2024.02.16 |
Swift No.26 (0) | 2024.02.08 |