티스토리 뷰
저번 글에 이어서 제네릭에 대해서 더 알아보자
연관 타입 (Associated Types)
연관 타입은 프로토콜의 일부분으로 타입에 플레이스홀더 이름을 부여한다.
다시말해 특정 타입을 동적으로 지정해 사용할 수 있다.
연관 타입의 실 사용 (Associated Types in Action)
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
위와 같이 associatedtype을 사용할 수 있다.
이렇게 지정하면 Item은 어떤 타입도 될 수 있다.
struct IntStack: Container {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
위 코드에서는 Item을 Int형으로 선언해 사용했다.
Element형으로 지정하여 바꿔보자.
struct Stack<Element>: Container {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
typealias Item = Element
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
존재 하는 타입에 연관 타입을 확장 (Extending an Existing Type to Specify an Associated Type)
extension Array: Container {}
위와 같이 기존의 타입 Array에 extension을 사용하여 특정 연관타입을 추가할 수 있다.
이것이 가능한 이유는 Array타입은 Container에 선언된 append, count, subscript가 모두 정의되어 있기 때문이다.
프로토콜에 연관 타입의 제한 사용하기 (Using a Protocol in Its Associated Type’s Constraints)
연관 타입을 적용할 수 있는 타입에 조건을 걸어 제한을 둘 수 있다. 조건을 붙일 때는 where구문을 사용한다. 이 조건에는 특정 타입인지, 특정 프로토콜을 따르는지 등의 여부를 알 수 있다.
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
위 코드는 Suffix가 SuffixableContainer 프로토콜을 따르고 Item타입이 반드시 Container의 Item타입이어야 한다는 조건을 추가한 것이다.
위에서 Stack의 연관 타입 Suffix 또한 Stack이다. Stack의 suffix의 실행으로 또 다른 Stack을 반환하게 된다.
그래서 IntStack에 Stack을 사용해 SuffixableContainer을 따르는 extension을 선언할 수도 있다.
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack<Int>.
}
제네릭의 where절 (Generic Where Clauses)
제네릭에서도 where절을 사용할 수 있다.
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
}
Container C1과 C2를 비교하여 모든 값이 같을 때 true를 반환하는 코드이다.
위에서 Container가 달라도 상관없다. 단지 각 Container의 같은 인덱스의 모든 값이 같다면 true를 얻게 된다.
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// Prints "All items match."
같은 값을 가지고 있지만 하나는 Stack, 하나는 Array Container이다.
Container가 다르지만 값이 같기 때문에 true를 반환한다.
Where절을 포함하는 제네릭의 익스텐션 (Extensions with a Generic Where Clause)
제네릭의 extension을 추가할 때 where을 사용할 수 있다.
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
위 코드는 제네릭(Stack)에 extension으로 isTop 함수를 추가하면서 이 함수가 추가되는 Stack은 반드시 Equatable 프로토콜을 따라야한다고 제한을 부여한 코드이다.
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
String은 Equatable 프로토콜을 따르기때문에 true에 해당하는 분기가 실행된다.
Equatable 프로토콜을 따르지 않는다면 어떻게 될지 한번 만들어보자
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
Equatable을 따르지 않는 Stack에서 isTop함수를 실행하면 에러가 발생한다.
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
위 코드는 Container의 Item이 Equatable을 따라야 한다고 제한을 부여한 코드이다.
startsWith 함수의 인자인 Item은 Container의 특정 아이템이 입력한 Item으로 시작하는지 비교하기 위해서는 Container의 첫 아이템이 입력한 Item과 같은지 비교해야 하기 때문에 Equatable프로토콜을 따라야 한다.
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
Container 프로토콜을 따르는 Array에서 startsWith 함수를 실행한다.
Int 값인 42는 Equatable 프로토콜을 따르므로 startsWith 함수가 실행되고 42는 배열의 첫 번째 값인 9과 같지 않기 때문에 같지 않다는 분기가 실행된다.
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"
where 절에서 특정 프로토콜을 따르는 것 뿐만 아니라 특정 값 타입인지 비교하는 구분을 사용할 수도 있다.
위는 Item이 Double형인지 비교한 코드이다.
Container의 Item [1260.0, 1200.0, 98.6, 37.0]이 Double형이기 때문에 익스텐션에서 구현된 average()를 사용할 수 있습니다.
제네릭의 연관 타입에 where절 적용 (Associated Types with a Generic Where Clause)
연관 타입에도 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
}
연관 타입 Iterator에 Iterator의 Element가 Item과 같아야 한다는 조건을 건 예다.
protocol ComparableContainer: Container where Item: Comparable { }
다른 프로토콜을 상속하는 프로토콜에도 where절로 조건을 부여할 수 있다.
제네릭의 서브스크립트 (Generic Subcript)
제네릭의 서브스크립트에도 조건을 걸 수 있다.
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
}
}
위 예제는 Indices.Iterator.Element가 Int 형이어야 한다는 조건을 건 예다.
'iOS > Swift' 카테고리의 다른 글
[Swift] Strong, Weak, Unowned 참조 (0) | 2022.12.05 |
---|---|
[Swift] Delegate, Notification 차이 (0) | 2022.11.22 |
[Swift] Generic (1) (0) | 2022.11.19 |
[Swift] GCD(Grand Central Dispatch) (0) | 2022.11.14 |
[Swift] Class와 Struct의 차이 (0) | 2022.11.09 |
- Total
- Today
- Yesterday
- Protocol
- AWS
- navigation
- onTapGesture
- SwiftUI
- frame과 bounds 차이
- Generic
- Login
- MVVM
- CodingTest
- 의미있는이름
- strcut
- enumerations
- Swift
- ChatGPT
- delegate
- 카메라
- ObservedObject
- tabview
- file private
- Xcode
- 곰튀김
- rxswift
- IOS
- Custom
- OCR
- docker
- Git
- kakao
- AWS Fargate
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |