오늘은 KeyPath라는 것을 알아보겠습니다. KeyPath는 사실 개발하면서 현재 많이 쓰지 않는 기능이긴 하지만 SwiftUI에서 간혹 보곤 합니다.
LazyVGrid(columns: columns) {
ForEach(images, id: \.self) { image in
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 150)
}
}
\.self 이게 바로 KeyPath입니다. 사용해 보신 적이 있으실 겁니다.
간단한 구조체 하나를 만들면서 설명 시작하겠습니다.
struct Streamer {
var name: String
var subscriber: Int
}
let streamer = Streamer(name: "nogu", subscriber: 5)
Steamer는 자신의 이름과 구독하고 있는 구독자수라는 속성 두 개를 가지고 있습니다. 구독자는 아래처럼 변경될 수 있겠죠!
var streamer = Streamer(name: "nogu", subscriber: 5)
print(streamer) // Streamer(name: "nogu", subscriber: 5)
// 구독자 수 증가 !
streamer = Streamer(name: streamer.name, subscriber: streamer.subscriber + 1)
print(streamer) // Streamer(name: "nogu", subscriber: 6)
함수를 만들어서 만들면 좀 더 편하게 만들 수도 있을 것 같습니다!
func increaseSubsriber(_ function: @escaping (Int) -> Int) -> (Streamer) -> Streamer {
return { oldStreamer in
return Streamer(name: oldStreamer.name, subscribers: function(oldStreamer.subscribers))
}
}
print(streamer) //Streamer(name: "nogu", subscribers: 5)
// 구독자 수 증가 !
streamer = increaseSubsriber { $0 + 1 }(streamer)
print(streamer) //Streamer(name: "nogu", subscribers: 6)
그럼 Keypath를 어떻게 사용할 수 있을까요?
\Streamer.subscribers //KeyPath<Streamer, Int>
\를 사용하여 KeyPath를 얻을 수 있습니다. KeyPath <key, value>라고 생각하면 쉽게 이해가 가능할 것 같기도 하네요..
keyPath를 활용한 Getter
streamer[keyPath: \Streamer.subscribers] // return Int
KeyPath를 활용한 Setter
var copyStreamer = streamer
copyStreamer[keyPath: \Streamer.subscribers] = 8
KeyPath로 프로퍼티에 접근해서 값을 바꾸고 가져올 수 있는 Getter, Setter를 할 수 있다는 것을 알 수 있었습니다.
지금까지 방법만 다르다는 것을 우리는 알 수 있었고 이제 그 외 KeyPath를 활용할 수 있는 것을 알아볼게요!
struct Animal {
var name: String
var footCount: Int
}
let animals = [
Animal(name: "Cat", footCount: 4),
Animal(name: "Dog", footCount: 4),
Animal(name: "Cow", footCount: 4),
Animal(name: "Snake", footCount: 0),
Animal(name: "Bird", footCount: 2)
]
이때 Map을 사용한다면
아래와 같이 사용할 텐데요
animals.map{ $0.footCount } //print [4, 4, 4, 0, 2]
keypath를 활용한 새로운 Map을 만들어서 사용하면 어떻게 될까요?
extension Sequence {
func map<Value>(_ keyPath: KeyPath<Element,Value>) -> [Value] {
return self.map{$0[keyPath: keyPath]}
}
}
animals.map(\.footCount) //print [4, 4, 4, 0, 2]
달러표시를 하지 않고 \.footCount를 사용할 수 있도록 하였습니다. 좀 더 간단해진 것을 알 수 있습니다.
또한 해당 링크에서는 == 를 사용하여 연산자를 재정의하여 고차함수를 사용하는 것을 소개하고 있습니다.
https://www.swiftbysundell.com/articles/custom-query-functions-using-key-paths/
Creating custom query functions using key paths | Swift by Sundell
Being a fairly strict, statically compiled language, it might not initially seem like Swift offers much in terms of syntax customization, but that’s actually quite far from the case. Through features like custom and overloaded operators, key paths, funct
www.swiftbysundell.com
func ==<T, V: Equatable>(lhs: KeyPath<T, V>, rhs: V) -> (T) -> Bool {
return { $0[keyPath: lhs] == rhs }
}
animals.filter(\.footCount == 4)
/*print
[
Animal(name: "Cat", footCount: 4),
BackTracking.Animal(name: "Dog",
footCount: 4),
BackTracking.Animal(name: "Cow", footCount: 4)
]
*/
여기서 == 같이 연산자를 모두 재정의하여 사용할 수 있지만 기존의 연산자를 사용할 수 있는 것도 만들고 싶어서
새로운 함수를 만들어 보았습니다.
extension Sequence {
func filter<Value: Equatable>(_ lhs: KeyPath<Element, Value>,
_ f: (Value, Value) -> Bool,
_ rhs: Value) -> [Element] {
self.filter {f($0[keyPath: lhs], rhs) }
}
}
animals.filter(\.footCount, ==, 4)
/*print
Animal(name: "Cat", footCount: 4),
BackTracking.Animal(name: "Dog",
footCount: 4),
BackTracking.Animal(name: "Cow", footCount: 4)
]
*/
아래와 같이 Chain 방식으로 사용가능
animals
.map(\.footCount)
.filter(\.self, ==, 4)
//print
//[4, 4, 4]
'Swift' 카테고리의 다른 글
Array에서 중복 제거하는 방법 (0) | 2023.04.19 |
---|---|
Array의 index를 안전하게 처리하는 방법 (2) | 2023.04.18 |
FP에서 Setter 사용법 (0) | 2022.12.10 |
Custom Operator => (0) | 2022.12.02 |
Currying (2) | 2022.11.30 |