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 برای اتصال View والد و فرزند استفاده میکند. والد یک 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 نمیداند روی چه نوع شیئی فراخوانی میشود و چه نوع مقداری برمیگرداند. این برای ذخیره KeyPathهای ناهمگن در مجموعهها و برای بازتاب مفید است.
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:)) کارآمدتر است. کلیدهای رشتهای از طریق مکانیزم زمان اجرای Objective-C محاسبه میشوند، شامل تجزیه رشته، جستجو در زمان اجرا و تبدیل نوع. KeyPath یک مکانیزم ایستا مبتنی بر ساختارهای بومی Swift است.
بر اساس آزمایشها، KeyPath به طور متوسط 10-20 برابر سریعتر از value(forKey:) برای دسترسی به ویژگیها است. تفاوت به دلیل عدم وجود جستجوی زمان اجرا و توزیع پویا است. KeyPath با بهینهسازی کافی کامپایلر مستقیماً به offset ویژگی در حافظه دسترسی پیدا میکند.
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 برای ذخیره و ارسال KeyPathهای بهدستآمده به صورت ایستا استفاده کرد. برای دسترسی پویا به ویژگیها از طریق رشته، از value(forKey:) با Objective-C runtime استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.