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 — ตัวแปรแบบ type-erased สำหรับเก็บ KeyPath ในคอลเล็กชัน

KeyPath ใน Swift คืออะไร?

KeyPath เป็นชนิดจากไลบรารีมาตรฐานของ Swift ที่แทนพาธไปยังพร็อพเพอร์ตี้ของอ็อบเจกต์ ต่างจากการเข้าถึงพร็อพเพอร์ตี้ด้วยตนเองผ่าน dot notation (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: ชนิดค่าถูกส่งโดยค่า ดังนั้นการเปลี่ยนแปลงจึงต้องการความสามารถในการเปลี่ยนแปลงของคอนเทนเนอร์

KeyPath ใน Combine และ SwiftUI

KeyPath มีบทบาทสำคัญใน Combine และ SwiftUI ใน Combine ตัวดำเนินการ assign ใช้ KeyPath เพื่อผูกค่าของผู้เผยแพร่กับพร็อพเพอร์ตี้ของอ็อบเจกต์ ใน SwiftUI Binding ใช้ KeyPath สำหรับการเชื่อมโยงสองทางระหว่างโมเดลและมุมมอง

Assign ใน Combine

ตัวดำเนินการ 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 ที่สร้างขึ้นผ่าน Binding(get:set:) หรือ projection ($) KeyPath ช่วยให้ SwiftUI ติดตามการเปลี่ยนแปลงโดยอัตโนมัติและอัปเดต UI โดยไม่ต้องใช้ความพยายามเพิ่มเติม

การใช้ KeyPath เพื่อการเรียงลำดับและกรอง

KeyPath ช่วยให้เขียนฟังก์ชันการเรียงลำดับและกรองที่ยืดหยุ่นและปลอดภัยด้านชนิด แทนที่จะส่งคลosureทุกครั้ง คุณสามารถส่ง 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 ใดๆ โดยไม่ต้องเขียนคลosure ในการเรียงลำดับตาม นามสกุล เพียงเปลี่ยน KeyPath: \Person.lastName หากต้องการเรียงลำดับตามคีย์ที่กำหนดเอง (เช่น ความยาวชื่อ) จะใช้ map ภายใน — นักพัฒนายังสามารถเขียนคลosure ของตนเองสำหรับกรณีที่ไม่ได้มาตรฐาน

AnyKeyPath แบบ type-erased

AnyKeyPath เป็นชนิดพื้นฐานในลำดับชั้น KeyPath ที่ลบข้อมูลเกี่ยวกับชนิดรากและค่าเฉพาะ AnyKeyPath ไม่รู้ว่าถูกเรียกบนชนิดอ็อบเจกต์ใดหรือส่งคืนชนิดค่าใด ซึ่งมีประโยชน์สำหรับการเก็บ KeyPaths ต่างชนิดในคอลเล็กชันและสำหรับการสะท้อน

การใช้ 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 เทียบกับคีย์สตริง

KeyPath มีประสิทธิภาพมากกว่าคีย์สตริง KVC (value(forKey:)) อย่างมาก คีย์สตริงถูกแก้ไขผ่านกลไก runtime ของ Objective-C ซึ่งรวมถึงการแยกวิเคราะห์สตริง การค้นหาใน runtime และการแปลงชนิด KeyPath เป็นกลไกแบบสถิตที่อิงตามโครงสร้างดั้งเดิมของ Swift

การเปรียบเทียบประสิทธิภาพ

ตามเกณฑ์มาตรฐาน KeyPath โดยเฉลี่ย เร็วกว่า 10-20 เท่า เมื่อเทียบกับ value(forKey:) สำหรับการเข้าถึงพร็อพเพอร์ตี้ ความแตกต่างเกิดจากการไม่มีการค้นหาใน runtime และการจัดส่งแบบไดนามิก KeyPath เข้าถึง offset ของพร็อพเพอร์ตี้ในหน่วยความจำโดยตรงด้วยการเพิ่มประสิทธิภาพคอมไพเลอร์ที่เพียงพอ

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 เป็นทางเลือกที่ถูกต้องเพียงทางเดียว

คำถามที่พบบ่อย

KeyPath ใน Swift พูดง่ายๆ คืออะไร?

KeyPath คือตัวชี้ไปยังพร็อพเพอร์ตี้ของอ็อบเจกต์ที่สามารถส่งเป็นค่าปกติได้ แทนที่จะเขียน person.name คุณสร้างพาธ \Person.name และใช้มันเพื่ออ่านหรือเขียนพร็อพเพอร์ตี้บนอินสแตนซ์ Person ใดๆ

ความแตกต่างระหว่าง KeyPath, WritableKeyPath และ ReferenceWritableKeyPath คืออะไร?

KeyPath — อ่านอย่างเดียว WritableKeyPath — สำหรับอ่านและเขียนชนิดค่า (struct) ผ่านบริบท inout ReferenceWritableKeyPath — สำหรับอ่านและเขียนชนิดอ้างอิง (class) โดยไม่มีเงื่อนไขเพิ่มเติม

KeyPath ใช้ที่ไหนใน Swift?

KeyPath ถูกใช้อย่างแพร่หลายใน Combine (assign(to:on:)), SwiftUI (Binding, FocusState), การเรียงลำดับคอลเล็กชัน (sorted(by:)), Core Data (NSSortDescriptor) และสำหรับการเข้าถึงพร็อพเพอร์ตี้ที่ซ้อนกันอย่างปลอดภัย

KeyPath ปลอดภัยกว่า value(forKey:) หรือไม่?

ใช่ KeyPath ปลอดภัยด้านชนิดอย่างสมบูรณ์ ข้อผิดพลาดในพาธ (พิมพ์ผิด ชนิดผิด) ถูกตรวจพบในเวลาคอมไพล์ ไม่ใช่ในรันไทม์ value(forKey:) ใช้สตริงและอาจล้มเหลวด้วย NSUndefinedKeyException หากคีย์ไม่มีอยู่

สามารถสร้าง KeyPath แบบไดนามิกได้หรือไม่?

ไม่โดยตรง — Swift ไม่สนับสนุนการสร้าง KeyPath แบบไดนามิกจากสตริง อย่างไรก็ตาม คุณสามารถใช้ AnyKeyPath เพื่อเก็บและส่ง KeyPaths ที่ได้รับแบบสแตติก สำหรับการเข้าถึงพร็อพเพอร์ตี้แบบไดนามิกโดยสตริง ให้ใช้ value(forKey:) กับ runtime ของ Objective-C

สรุป

  • KeyPath — พาธแบบมีชนิดไปยังพร็อพเพอร์ตี้ แสดงเป็นชนิดค่าชั้นหนึ่ง
  • ลำดับชั้นของ KeyPath รวมถึง WritableKeyPath และ ReferenceWritableKeyPath สำหรับระดับการเข้าถึงที่แตกต่างกัน
  • Combine และ SwiftUI ใช้ KeyPath อย่างแข็งขันสำหรับการผูกแบบรีแอกทีฟ
  • การเรียงลำดับและกรองผ่าน KeyPath ช่วยเพิ่มความอ่านง่ายและความปลอดภัยด้านชนิดของโค้ด
  • AnyKeyPath — ตัวแปรแบบ type-erased สำหรับเก็บ KeyPaths ต่างชนิดในคอลเล็กชัน
  • ประสิทธิภาพของ KeyPath สูงกว่า value(forKey:) 10-20 เท่า

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม