우리 파인더를 보면 요런 식으로 폴더에 대한 경로를 볼 수 있죠?
KeyPath는 지금과 유사하게 `어떤 특정한 타입`이 가진 프로퍼티에 접근할 때 `경로를 통해서 접근`을 하는 표현이에요.
SwiftUI에서 `ForEach`를 사용해 반복적인 뷰들을 그려줄 때
struct Item: Identifiable {
var id: UUID
var name: String
}
let items: [Item] = [ ... ]
ForEach(items, id: \.self) { item in
Text(item.name)
}
지금처럼 id: \.self
를 작성하기도 하고, id: \.id
를 작성하기도 하죠?
위 처럼 작성할 때 생략된 부분은 id: \Item.self
혹은 id: \Item.id
입니다.
\.self
를 사용했을 땐 각각의 항목들을 항목 자체로 식별하겠다는 거고\.id
의 경우 Item 모델이 가진 id라는 프로퍼티 경로
로 식별하겠다는 거에요!
이 프로퍼티의 경로라는게 잘 감이 안 잡히죠?
우리 어머니께서 냉장고의 냉장실의 위 선반에 있는 락앤락 반찬통에 있는 김치
꺼내 먹어라는 문자를 보내셨을 때 저희가 먹을 반찬
타입에 대한 키 패스를 어머니께서 알려주셨다고 할 수 있겠네요.
키패스(Key-Path) 표현식은 Swift 프로그래밍에서 중요한 요소 중 하나에요. 이 글에서는 키패스 표현식이 무엇이며 어떻게 사용되는지를 자세히 알아볼게요.
1. 키패스 표현식의 기본 구조
- 키패스 표현식은 다음과 같은 형식을 가집니다:
\<#타입 이름#>.<#경로#>
- 타입 이름: 이것은 구체적인 타입의 이름을 나타냅니다. 이 때, generic 파라미터를 포함할 수 있습니다. 예를 들어, `String`, `[Int]`, 또는 `Set<Int>`와 같은 형태가 될 수 있습니다.
- 경로: 이 부분은 속성 이름, 서브스크립트, 옵셔널 체이닝 표현식, 강제 언래핑 표현식으로 구성됩니다. 각각의 키패스 컴포넌트는 원하는 만큼 순서대로 반복할 수 있으며, 이들을 조합하여 복잡한 경로를 지정할 수 있습니다.
2. 값에 접근하기
키패스 표현식을 사용하여 값을 접근하려면 해당 키패스를 배열이나 객체에 전달하면 됩니다.
struct SomeStructure {
var someValue: Int
}
let s = SomeStructure(someValue: 12)
let pathToProperty = \SomeStructure.someValue
let value = s[keyPath: pathToProperty]
// value는 12
3. 타입 이름 생략하기
- 타입 추론이 가능한 경우, 타입 이름은 생략할 수 있습니다. 아래의 코드에서는 `\SomeClass.someProperty` 대신 `\.someProperty`를 사용하고 있습니다.
class SomeClass: NSObject {
@objc dynamic var someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
}
}
let c = SomeClass(someProperty: 10)
c.observe(\.someProperty) { object, change in
// ...
}
4. Identity Key-Path: .self
키패스의 경로 부분에 self를 사용하여 identity key-path를 생성할 수 있습니다. 이는 전체 인스턴스를 가리키며, 변수 내의 데이터에 쉽게 접근하거나 변경할 수 있습니다.
var compoundValue = (a: 1, b: 2)
// (a: 10, b: 20)으로 대체 가능
compoundValue[keyPath: \.self] = (a: 10, b: 20)
5. 다중 프로퍼티 접근
키 패스 경로에는 여러 프로퍼티 이름을 포함할 수 있습니다. 이를 통해 중첩된 타입 내의 속성에 접근할 수 있어요.
struct OuterStructure {
var outer: SomeStructure
init(someValue: Int) {
self.outer = SomeStructure(someValue: someValue)
}
}
let nested = OuterStructure(someValue: 24)
let nestedKeyPath = \OuterStructure.outer.someValue
let nestedValue = nested[keyPath: nestedKeyPath]
// nestedValue는 24
6. Subscript 활용
키패스 경로 내에서는 서브스크립트를 활용할 수 있습니다.
다만, 서브스크립트의 파라미터 타입은 Hashable 프로토콜을 준수해야 합니다.
let greetings = ["hello", "hola", "bonjour", "안녕"]
let myGreeting = greetings[keyPath: \[String].[1]]
// myGreeting은 'hola'
7. 클로져와 함께 사용
키패스를 사용한 서브스크립트의 값은 변수나 리터럴로 지정할 수 있습니다. 값은 값(semantics)을 사용하여 키패스에서 캡처됩니다. 다음 코드에서는 키패스 표현식과 클로져 내에서 변수 `index`를 활용하여 값을 접근합니다.
var index = 2
let path = \[String].[index]
let fn: ([String]) -> String = { strings in strings[index] }
print(greetings[keyPath: path])
// "bonjour" 출력
print(fn(greetings))
// "bonjour" 출력
// 'index' 값을 변경해도 'path'에 영향을 미치지 않음
index += 1
print(greetings[keyPath: path])
// "bonjour" 출력
// 'fn'은 'index'를 캡처하므로 새로운 값 사용
print(fn(greetings))
// "안녕" 출력
8. 부작용
키패스 표현식의 부작용은 해당 표현식이 평가되는 시점에만 발생합니다. 따라서 함수 호출이 키패스 표현식 내에서 발생하더라도, 해당 함수는 표현식을 평가할 때 한 번만 호출되며, 키패스가 다시 사용될 때마다 다시 호출되지 않습니다.
func makeIndex() -> Int {
print("Made an index")
return 0
}
// 아래 줄은 makeIndex()를 호출합니다.
let taskKeyPath = \[Task][makeIndex()]
// "Made an index" 출력
// 'taskKeyPath'를 사용해도 makeIndex()를 다시 호출하지 않음
let someTask = toDoList[keyPath: taskKeyPath]
9. Objective-C와의 상호작용
Objective-C API와 상호작용하는 코드에서 키패스를 사용할 때 더 많은 정보가 필요한 경우, [Using Objective-C Runtime Features in Swift] 문서를 참고하세요. 또한, 키패스를 사용한 키-값 코딩과 키-값 관찰에 대한 자세한 내용은 [Key-Value Coding Programming Guide]와 [Key-Value Observing Programming Guide]를 참고하세요.
키패스 표현식은 Swift 프로그래밍의 강력한 기능 중 하나이며, 복잡한 데이터 모델을 다룰 때 효과적으로 사용할 수 있습니다. 이러한 기능을 활용하여 코드를 간결하고 읽기 쉽게 작성할 수 있습니다.