KeyPath
SwiftUI 에서 List에서 사용해보고, Combine에서도 assign
메서드에서 사용을 했었던 KeyPath를 알아보자
Swift4!! 에 도입된 기능!!
객체의 프로퍼티나 서브스크립트에 대한 경로를 type-safe하게 참조하는 기능
컴파일 타임에 프로퍼티에 접근하거나 수정할 수있고, 코드 안전성과 가독성을 높여준다.
\\<#typename#>.<#path#>
KeyPath는 역슬래쉬 타입이름.프로퍼티 와 같은 형태로 사용된다.
struct A {
var value: Int = 10
}
class Person {
var name: String = "NAME"
}
let a1 = A()
print(a1[keyPath: \\.value])
let a2 = A()
let path = \\A.value
print(a2[keyPath: path]) // 10
let person = Person()
print(person[keyPath: \\.name])
KeyPath의 특징
- 타입 안정성 - 컴파일 타임에 타입검사가 이뤄져 런타임 오류를 줄일 수 있다.
- 읽기, 쓰기 가능 - 아무렇게나 다 가능!! 이건 아니고, KeyPath로 사용한 해당 객체의 프로퍼티가 변경이 가능하면 KeyPath가
WriteableKeyPath
타입으로 저장되고, 아니면 그냥 읽기만 가능한 기본KeyPath
타입으로 저장된다. - 여러 타입이 있지만, 그냥 사용할 객체의 KeyPath 타입 생략하고 넣어주면 알아서 컴파일러가 추론해준다. 하지만 알긴 해야겠죠
KeyPath
- 읽기만 가능WritableKeyPath
- 읽고 쓰기 가능ReferenceWritableKeyPath
- 읽고 쓰기 (참조타입에서)
struct A {
var value: Int = 10
}
var a = A()
// 직접 타입을 이렇게 지정할수도 있음
// <KeyPath를 사용할 객체, 반환할 타입>
let writablePath: WritableKeyPath<A, Int> = \\A.value
a[keyPath: writablePath] = 99
print(a[keyPath: \\.value])
위 코드처럼 타입을 명시해서 사용할 수 있기는 한디.. 객체의 프로퍼티가 수정 불가능할때는 오류가 날것임. KeyPath가 불가능을 가능으로 만드는것은 아니다.
이에 대한 자세한 설명은 링크에 블로그 글을 참고!! 정말 상세히 정리해주셨음 (나는 그냥 간단요약만..)
https://0urtrees.tistory.com/362
https://eunjin3786.tistory.com/590
KeyPath를 다양하게 사용하는 예제
타입이 Hashable인 경우에는 subscript를 keyPath에서도 사용가능
let temp = ["a", "b", "c"]
print(temp[keyPath: \\[String].[1]])
optional chaining, forced unwrapping을 사용가능.
let temp = ["a", "b", "c"]
print("Count: \\(temp[keyPath: \\[String].first?.count)")
클로저에서도 KeyPath 사용가능 (EX - (Type) → Value 인 경우)
struct Task {
var description: String
var completed: Bool
}
var toDoList = [
Task(description: "Practice ping-pong.", completed: false),
Task(description: "Buy a pirate costume.", completed: true),
Task(description: "Visit Boston in the Fall.", completed: false),
]
let descriptions = toDoList.filter(\\.completed).map(\\.description)
print(descriptions)
문제점
KeyPath는 Static member를 참조할 수 없다!!
KeyPath를 알아보다 KVC, KVO라는것도 알게됐는데, 이것도 뭔가 비슷하게 KeyPath를 사용하는거 같아서 정리해봤다.
KVC (Key Value Coding)
class A: NSObject {
@objc var value: Int = 0
}
let a = A()
a.setValue(5, forKey: "value")
if let value = a.value(forKey: "value") as? Int {
print(value) // 5
}
문자열 키를 사용해서 객체 프로퍼티에 접근하고 수정할 수 있음.
런타임에 프로퍼티 이름을 동적으로 결정해야 할 때 유용하다.
KVC는 Obj-C 런타임에 의존하므로 NSObject를 상속해야하고, 프로퍼티 앞에 @objc 를 붙여서 사용
주로 Notification, UserDefaults에서 자주 사용해봤을것이다.
하지만 Swift에서는 타입 안정성과 컴파일 타임 검사를 선호하기에.. 이런 메커니즘은 최소화 하는걸 권장.. 한다는데요..
KVO (Key Value Observing)
class A: NSObject {
@objc dynamic var value: Int = 0
}
let a = A()
var observation: NSKeyValueObservation? = a
.observe(\\.value, options: [.new, .old]) { old, new in
if let newValue = new.newValue {
print("\\(newValue) 로 변경")
}
}
a.value = 5 // 5 로 변경
observation?.invalidate() // 옵저버를 해제
observation = nil // 메모리 해제
KVO는 객체의 특정 프로퍼티 변경을 관찰하고, 변경이 발생할 때 통지를 받는 매커니즘.
KVO 역시 Obj-C 런타임에 의존하고, NSObject를 상속하고 프로퍼티에 @objc dynamic
키워드가 붙어야한다.
KVC와 다르게 dynamic
이 붙는 이유는 동적으로 프로퍼티의 변경을 관찰하고 반응할 수 있게 하기 위한 키워드라 보면 된다.
NotificationCenter에 Observer를 만드는것과 유사하다고 생각된다.
비슷한 매커니즘으로 NotificationCenter, Delegate 패턴이 있는데 이 차이는 표로 정리
Delegate패턴은 UI이벤트 처리, 데이터소스 위임등 특정 객체에게 동작을 위임할때 사용하고,
Notification은 로그인 상태변경, 네트워크 상태변경 등 여러 객체가 이벤트를 동시에 받아야할 때 사용하고,
KVO는 모델 값 변경 감지와 같이 속성이 변경될 때 자동으로 감지해야할때 사용하는것이 좋다.
결론!
KeyPath 와 KVC/KVO는 밀접한 연관이 있지만, 차이점을 정리해보자
- KVC/KVO는 Obj-C 런타임에 의존함 (그래서 NSObject, @objc 등 키워드를 사용해야 쓸 수 있음)
- KeyPath는 Swift 기능으로 위와같은 의존성 없음!! 그냥 사용 가능!
- KVC는 문자열을 사용해 프로퍼티에 접근하므로 런타임 오류 가능성 있음!!
- KeyPath는 컴파일 타임에 타입 검사를 수행하므로 더 안전!!
- KVC/KVO는 런타임에 동적으로 프로퍼티에 접근 또는 관찰하는데 사용됨
- KeyPath는 컴파일 타임에 프로퍼티 경로를 지정해 안전하고 효율적인 코드를 작성하는데 중점을 둔다!!