KeyPath: 주요 개념 및 타입화된 접근

저자: IT Sectr 게시일: 2026-06-18 읽는 시간: 10 분

KeyPath는 Swift에서 객체의 프로퍼티에 대한 타입화된 경로이며, 일급 값으로 표현됩니다. KVC(Key-Value Coding)의 문자열 키와 달리 KeyPath는 컴파일 타임에 컴파일러에 의해 검사됩니다. 컴파일러는 루트 객체의 타입, 프로퍼티의 타입을 알고 경로가 존재함을 보장할 수 있습니다. KeyPath는 Combine에서 반응형 바인딩, SwiftUI에서 양방향 바인딩, 표준 라이브러리에서 컬렉션 정렬 및 필터링에 사용됩니다. Apple Developer, 2025에 따르면 KeyPath는 Swift에서 함수형 프로그래밍의 기본 구성 요소입니다.

주요 포인트

  • KeyPath — 프로퍼티에 대한 타입화된 경로, 컴파일러에 의해 검증됨
  • WritableKeyPath — 쓰기 접근이 가능한 프로퍼티를 위한 KeyPath의 하위 타입
  • ReferenceWritableKeyPath — 참조 타입의 가변 프로퍼티용
  • Combine은 KeyPath를 사용하여 퍼블리셔를 객체 프로퍼티에 바인딩
  • AnyKeyPath — 컬렉션에 KeyPath를 저장하기 위한 타입 소거 변형

Swift에서 KeyPath란 무엇인가?

KeyPath는 Swift 표준 라이브러리의 타입으로, 객체의 프로퍼티에 대한 경로를 나타냅니다. 점 표기법(object.property)이나 문자열 키(value(forKey:))를 통해 수동으로 프로퍼티에 접근하는 것과 달리 KeyPath는 일급 객체입니다: 인수로 전달하거나, 변수에 저장하거나, 결합할 수 있습니다.

KeyPath 구문

KeyPath는 백슬래시와 프로퍼티 경로로 작성됩니다: \Person.name. 경로는 중첩될 수 있습니다: \Person.address.city. 컴파일러는 Person에 name 프로퍼티가 있고 name이 실제로 String인지 확인합니다. 프로퍼티가 존재하지 않거나 타입이 잘못된 경우 코드는 컴파일되지 않습니다.

swift
struct Person: Codable {
    let name: String
    let age: Int
    var isActive: Bool
}

// KeyPath(읽기 전용)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]

KeyPath를 통한 프로퍼티 접근은 서브스크립트 object[keyPath: path]를 통해 이루어집니다. 읽기 전용 프로퍼티의 경우 KeyPath가 사용됩니다. 프로퍼티가 가변(var)인 경우 WritableKeyPath를 사용할 수 있습니다. 참조 타입의 경우 ReferenceWritableKeyPath를 사용하면 KeyPath를 통해 직접 프로퍼티를 수정할 수 있습니다.

KeyPath 계층 구조: WritableKeyPath 및 ReferenceWritableKeyPath

KeyPath에는 프로퍼티에 대한 접근 수준을 반영하는 하위 타입 계층 구조가 있습니다. 기본 계층 구조는 다음과 같습니다: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. 각 하위 타입은 기능을 추가합니다.

KeyPath 접근 수준

KeyPath — 읽기 전용. WritableKeyPath — 값 타입(struct)의 읽기 및 쓰기. ReferenceWritableKeyPath — 참조 타입(class)의 읽기 및 쓰기. Swift는 컨텍스트에 따라 자동으로 올바른 하위 타입을 선택합니다.

타입접근객체 타입변경
KeyPath읽기 전용모두불가
WritableKeyPath읽기 및 쓰기값 타입(inout)mutating 컨텍스트 통해
ReferenceWritableKeyPath읽기 및 쓰기참조 타입직접 할당
swift
// 값 타입용 WritableKeyPath
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true

// 클래스용 ReferenceWritableKeyPath
class User: NSObject {
    @objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"

중요한 차이점: 값 타입의 경우 KeyPath를 통한 설정에는 var(inout 컨텍스트)가 필요하지만, 참조 타입의 경우 가변 프로퍼티만 있으면 됩니다. 이는 Swift의 일반적인 의미 체계와 일치합니다: 값 타입은 값으로 전달되므로 변경에는 컨테이너의 가변성이 필요합니다.

Combine 및 SwiftUI의 KeyPath

KeyPath는 Combine과 SwiftUI에서 중추적인 역할을 합니다. Combine에서 assign 연산자는 KeyPath를 사용하여 퍼블리셔의 값을 객체의 프로퍼티에 바인딩합니다. SwiftUI에서 Binding은 모델과 뷰 간의 양방향 연결에 KeyPath를 사용합니다.

Combine의 Assign

assign(to:on:) 연산자는 ReferenceWritableKeyPath와 객체를 받습니다. 퍼블리셔가 값을 방출하면 지정된 프로퍼티에 자동으로 기록됩니다. 이는 수동 할당 없이 상태를 관리하는 선언적 방법입니다.

swift
import Combine

class SettingsViewModel: ObservableObject {
    @Published var volume: Float = 0.5
    var cancellables = Set<AnyCancellable>()
    
    func bindSlider(publisher: AnyPublisher<Float, Never>) {
        publisher
            .assign(to: \SettingsViewModel.volume, on: self)
            .store(in: &cancellables)
    }
}

SwiftUI에서 @Binding은 부모 뷰와 자식 뷰를 연결하기 위해 KeyPath를 사용합니다. 부모는 Binding(get:set:) 또는 프로젝션($)을 통해 생성된 Binding를 전달합니다. KeyPath를 통해 SwiftUI는 변경 사항을 자동으로 추적하고 추가 노력 없이 UI를 업데이트할 수 있습니다.

정렬 및 필터링을 위한 KeyPath 사용

KeyPath를 사용하면 유연하고 타입 안전한 정렬 및 필터링 함수를 작성할 수 있습니다. 매번 클로저를 전달하는 대신 컬렉션을 정렬하려는 프로퍼티에 대한 KeyPath를 전달할 수 있습니다. 이렇게 하면 코드가 더 깔끔해지고 중복이 줄어듭니다.

KeyPath별 제네릭 정렬 함수

함수 sorted는 KeyPath를 받습니다(Value: Comparable). 이는 정렬 프로퍼티가 비교를 지원함을 보장합니다. 컴파일러는 컴파일 타임에 이를 확인합니다.

swift
extension Sequence {
    func sorted<Value: Comparable>(
        by keyPath: KeyPath<Element, Value>,
        ascending: Bool = true
    ) -> [Element] {
        ascending
            ? self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
            : self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
    }
}

let sortedByName = people.sorted(by: \Person.name)
let sortedByAgeDesc = people.sorted(by: \Person.age, ascending: false)

이 구현은 클로저를 작성하지 않고도 모든 Comparable 프로퍼티로 정렬할 수 있습니다. 별로 정렬하려면 KeyPath만 변경하면 됩니다: \Person.lastName. 사용자 지정 키(예: 이름 길이)로 정렬해야 하는 경우 내부적으로 map이 사용됩니다 — 개발자는 비표준 사례를 위해 자체 클로저를 작성할 수 있습니다.

타입 소거된 AnyKeyPath

AnyKeyPath는 KeyPath 계층 구조의 기본 타입으로, 특정 루트 및 값 타입에 대한 정보를 지웁니다. AnyKeyPath는 어떤 객체 타입에서 호출되었는지나 어떤 값 타입을 반환하는지 알지 못합니다. 이는 이기종 KeyPath를 컬렉션에 저장하거나 리플렉션에 유용합니다.

AnyKeyPath 사용

AnyKeyPath는 다른 타입의 다른 프로퍼티에 대한 경로 목록을 저장해야 할 때 사용됩니다. 예를 들어 UI 구성기에서 각 경로가 특정 프로퍼티를 구성하고 프로퍼티 타입이 다를 수 있습니다. AnyKeyPath를 사용하면 모든 경로를 통일된 방식으로 작업할 수 있습니다.

swift
protocol Configurable {
    func apply(_ keyPath: any PartialKeyPath<Self>, value: any)
}

extension Configurable {
    func configure(_ pairs: (any KeyPath<Self, any>, any)...) {
        for (path, value) in pairs {
            apply(path, value: value)
        }
    }
}

extension Person: Configurable { }
// AnyKeyPath를 통한 프로퍼티 구성 허용

AnyKeyPath는 값을 검색할 때 타입 캐스팅이 필요합니다. 특정 타입이 지워졌기 때문입니다. 완전한 타입 안전성을 위해 타입화된 KeyPath가 항상 선호됩니다. AnyKeyPath는 직렬화나 리플렉션과 같이 컴파일 타임에 타입을 실제로 알 수 없는 경우를 위한 도구입니다.

KeyPath 성능 vs 문자열 키

KeyPath는 KVC 문자열 키(value(forKey:))보다 훨씬 효율적입니다. 문자열 키는 Objective-C 런타임 메커니즘을 통해 해결되며, 문자열 파싱, 런타임 조회 및 타입 캐스팅이 포함됩니다. KeyPath는 네이티브 Swift 구조체를 기반으로 하는 정적 메커니즘입니다.

성능 비교

벤치마크에 따르면 KeyPath는 프로퍼티 접근에 대해 value(forKey:)보다 평균 10-20배 빠릅니다. 차이는 런타임 조회와 동적 디스패치가 없기 때문입니다. KeyPath는 충분한 컴파일러 최적화를 통해 메모리의 프로퍼티 오프셋에 직접 접근합니다.

swift
func readWithKeyPath(person: Person) -> String {
    person[keyPath: \Person.name]
}

func readWithKVC(person: Person) -> String {
    person.value(forKey: "name") as! String
}

KVC는 느릴 뿐만 아니라 타입 안전하지 않습니다: “name” 문자열이 존재하지 않을 수 있고 as! String 캐스트가 충돌할 수 있습니다. KeyPath는 컴파일 타임에 프로퍼티의 존재와 타입 정확성을 보장합니다. 성능이 중요한 부분(애니메이션, 수천 개 항목이 있는 목록)에서는 KeyPath가 유일한 올바른 선택입니다.

자주 묻는 질문

간단히 말해 Swift에서 KeyPath란 무엇인가요?

KeyPath는 일반 값으로 전달할 수 있는 객체 프로퍼티에 대한 포인터입니다. person.name을 작성하는 대신 \Person.name 경로를 만들고 모든 Person 인스턴스에서 프로퍼티를 읽거나 쓰는 데 사용합니다.

KeyPath, WritableKeyPath 및 ReferenceWritableKeyPath의 차이점은 무엇인가요?

KeyPath — 읽기 전용. WritableKeyPath — inout 컨텍스트를 통해 값 타입(struct)을 읽고 쓰기 위한 것. ReferenceWritableKeyPath — 추가 조건 없이 참조 타입(class)을 읽고 쓰기 위한 것.

Swift에서 KeyPath는 어디에 사용되나요?

KeyPath는 Combine(assign(to:on:)), SwiftUI(Binding, FocusState), 컬렉션 정렬(sorted(by:)), Core Data(NSSortDescriptor) 및 중첩 프로퍼티에 대한 안전한 접근에 널리 사용됩니다.

KeyPath가 value(forKey:)보다 안전한가요?

, KeyPath는 완전히 타입 안전합니다. 경로 오류(오타, 잘못된 타입)는 런타임이 아닌 컴파일 타임에 발견됩니다. value(forKey:)는 문자열을 사용하며 키가 존재하지 않으면 NSUndefinedKeyException으로 충돌할 수 있습니다.

KeyPath를 동적으로 만들 수 있나요?

직접적으로는 불가능합니다 — Swift는 문자열에서 동적 KeyPath 생성을 지원하지 않습니다. 그러나 AnyKeyPath를 사용하여 정적으로 얻은 KeyPath를 저장하고 전달할 수 있습니다. 문자열을 통한 동적 프로퍼티 접근의 경우 Objective-C 런타임과 함께 value(forKey:)를 사용하세요.

요약

  • KeyPath — 프로퍼티에 대한 타입화된 경로, 일급 값 타입으로 표현
  • 계층 구조 KeyPath에는 다양한 접근 수준을 위한 WritableKeyPath와 ReferenceWritableKeyPath 포함
  • Combine 및 SwiftUI는 반응형 바인딩을 위해 KeyPath를 적극적으로 사용
  • 정렬 및 필터링 KeyPath를 통해 코드 가독성과 타입 안전성 향상
  • AnyKeyPath — 컬렉션에 이기종 KeyPath를 저장하기 위한 타입 소거 변형
  • 성능 KeyPath가 value(forKey:)보다 10-20배 높음

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기