KeyPath 알아보기 + KVC/KVO

728x90

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, Norification, KVO 차이점

Delegate패턴은 UI이벤트 처리, 데이터소스 위임등 특정 객체에게 동작을 위임할때 사용하고,

Notification은 로그인 상태변경, 네트워크 상태변경 등 여러 객체가 이벤트를 동시에 받아야할 때 사용하고,

KVO는 모델 값 변경 감지와 같이 속성이 변경될 때 자동으로 감지해야할때 사용하는것이 좋다.

결론!

KeyPath 와 KVC/KVO는 밀접한 연관이 있지만, 차이점을 정리해보자

  • KVC/KVO는 Obj-C 런타임에 의존함 (그래서 NSObject, @objc 등 키워드를 사용해야 쓸 수 있음)
  • KeyPath는 Swift 기능으로 위와같은 의존성 없음!! 그냥 사용 가능!
  • KVC는 문자열을 사용해 프로퍼티에 접근하므로 런타임 오류 가능성 있음!!
  • KeyPath는 컴파일 타임에 타입 검사를 수행하므로 더 안전!!
  • KVC/KVO는 런타임에 동적으로 프로퍼티에 접근 또는 관찰하는데 사용됨
  • KeyPath는 컴파일 타임에 프로퍼티 경로를 지정해 안전하고 효율적인 코드를 작성하는데 중점을 둔다!!
728x90