KeyPath هو مسار مقيد إلى خاصية كائن في Swift، ممثل كقيمة من الدرجة الأولى. بالمقارنة مع مفاتيح السلسلة في KVC (Key-Value Coding)، يتم التحقق من KeyPath بواسطة المجمع أثناء التجميع: يعرف المجمع نوع الكائن الجذري، نوع الخاصية ويمكنه ضمان وجود المسار. يستخدم KeyPath في Combine للربط التفاعلي، وفي SwiftUI للربط الثنائي الاتجاه، وفي المكتبة القياسية لفرز وتصفية المجموعات. وفقًا لـ Apple Developer, 2025، يعتبر KeyPath لبنة أساسية للبرمجة الوظيفية في Swift.
النقاط الرئيسية
KeyPath هو نوع من مكتبة Swift القياسية يمثل مسارًا إلى خاصية كائن. بالمقارنة مع الوصول اليدوي إلى خاصية باستخدام تدوين النقطة (object.property) أو مفتاح سلسلة (value(forKey:))، فإن KeyPath هو كائن من الدرجة الأولى: يمكن تمريره كوسيط، تخزينه في متغير وتركيبه.
يتم كتابة KeyPath باستخدام شرطة مائلة والمسار إلى الخاصية: \Person.name. يمكن أن يكون المسار متداخلًا: \Person.address.city. يتحقق المجمع من أن Person لديه خاصية name وأن name هو فعلًا من نوع String. إذا كانت الخاصية غير موجودة أو النوع خاطئًا، فإن الكود لن يتم تجميعه.
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 لديه تسلسل هرمي من الأنواع الفرعية التي تعكس مستوى الوصول إلى الخاصية. التسلسل الأساسي هو: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. كل نوع فرعي يضيف قدرات.
KeyPath — قراءة فقط. WritableKeyPath — قراءة وكتابة لأنواع القيمة (struct). ReferenceWritableKeyPath — قراءة وكتابة لأنواع المرجع (class). يختار Swift تلقائيًا النوع الفرعي المناسب بناءً على السياق.
| النوع | الوصول | نوع الكائن | التعديل |
|---|---|---|---|
| KeyPath | قراءة فقط | أي | لا |
| WritableKeyPath | قراءة وكتابة | نوع قيمة (inout) | عبر سياق mutating |
| ReferenceWritableKeyPath | قراءة وكتابة | نوع مرجع | تعيين مباشر |
// 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. في Combine، يستخدم عامل assign KeyPath لربط قيمة الناشر بخاصية كائن. في SwiftUI، يستخدم Binding KeyPath للربط الثنائي الاتجاه بين النموذج والعرض.
يقبل العامل assign(to:on:) ReferenceWritableKeyPath وكائنًا. عندما يصدر الناشر قيمة، تُكتب تلقائيًا في الخاصية المحددة. هذه طريقة تصريحية لإدارة الحالة دون تعيينات يدوية.
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
KeyPath يمكنه كتابة وظائف فرز وتصفية مرنة وآمنة من حيث النوع. بدلاً من تمرير إغلاق كل مرة، يمكنك تمرير KeyPath إلى الخاصية التي ترغب فرز المجموعة بها. هذا يجعل الكود أنظف ويقلل التكرار.
تقبل الدالة sorted
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 هو النوع الأساسي في تسلسل KeyPath الذي يمحو معلومات الأنواع المحددة للجذر والقيمة. AnyKeyPath لا يعرف على أي نوع كائن يتم استدعاؤه ولا أي نوع قيمة يعود. هذا مفيد لتخزين KeyPaths غير متجانسة في المجموعات وللانعكاس.
يستخدم AnyKeyPath عندما تحتاج إلى تخزين قائمة مسارات إلى خصائص مختلفة لأنواع مختلفة. على سبيل المثال، في مهيئات UI، حيث يقوم كل مسار بتكوين خاصية محددة، وقد تختلف أنواع الخصائص. يسمح AnyKeyPath بالعمل بشكل موحد مع جميع المسارات.
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
KeyPath أكثر كفاءة بشكل ملحوظ من مفاتيح السلسلة في KVC (value(forKey:)). تتم معالجة مفاتيح السلسلة من خلال آلية runtime لـ Objective-C، بما في ذلك تحليل السلسلة، البحث في runtime وتحويل الأنواع. KeyPath هو آلية ثابتة تعتمد على هياكل Swift الأصلية.
وفقًا لمعايير الاختبار، KeyPath أسرع بمتوسط 10-20 مرة من value(forKey:) للوصول إلى الخصائص. يعود الفرق إلى عدم وجود بحث runtime وتوزيع ديناميكي. يصل KeyPath مباشرة إلى إزاحة الخاصية في الذاكرة مع تحسين كاف للمجمع.
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 هو مؤشر إلى خاصية كائن يمكن تمريره كقيمة عادية. بدلاً من كتابة person.name، تقوم بإنشاء مسار \Person.name وتستخدمه لقراءة أو كتابة الخاصية في أي مثال من Person.
KeyPath — قراءة فقط. WritableKeyPath — لقراءة وكتابة أنواع القيمة (struct) عبر سياق inout. ReferenceWritableKeyPath — لقراءة وكتابة أنواع المرجع (class) دون شروط إضافية.
يستخدم KeyPath على نطاق واسع في Combine (assign(to:on:))، SwiftUI (Binding, FocusState)، فرز المجموعات (sorted(by:))، Core Data (NSSortDescriptor) وللوصول الآمن إلى الخصائص المتداخلة.
نعم، KeyPath آمن تمامًا من حيث النوع. يتم اكتشاف خطأ في المسار (خطأ مطبعي، نوع خاطئ) في وقت التجميع، لا في وقت التنفيذ. يستخدم value(forKey:) سلسل وقد يتعطل مع NSUndefinedKeyException إذا لم تكن المفتاح موجودًا.
لا مباشرة — Swift لا يدعم الإنشاء الديناميكي لـ KeyPath من سلسلة. ولكن يمكنك استخدام AnyKeyPath لتخزين وتمرير KeyPaths التي تم الحصول عليها بشكل ثابت. للوصول الديناميكي إلى الخصائص عبر سلسلة، استخدم value(forKey:) مع runtime لـ Objective-C.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.