본문 바로가기
Swift

KeyPath

by zinozino 2023. 1. 16.

 

오늘은 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