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 در مجموعه‌ها

KeyPath در Swift چیست؟

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 مطابقت دارد: انواع مقداری با مقدار منتقل می‌شوند، بنابراین تغییر نیاز به قابلیت تغییر ظرف دارد.

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 برای اتصال View والد و فرزند استفاده می‌کند. والد یک Binding ایجادشده از طریق Binding(get:set:) یا پروجکشن ($) را ارسال می‌کند. 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 در مقابل کلیدهای رشته‌ای

KeyPath به طور قابل توجهی از کلیدهای رشته‌ای KVC (value(forKey:)) کارآمدتر است. کلیدهای رشته‌ای از طریق مکانیزم زمان اجرای Objective-C محاسبه می‌شوند، شامل تجزیه رشته، جستجو در زمان اجرا و تبدیل نوع. KeyPath یک مکانیزم ایستا مبتنی بر ساختارهای بومی Swift است.

مقایسه عملکرد

بر اساس آزمایش‌ها، KeyPath به طور متوسط 10-20 برابر سریع‌تر از value(forKey:) برای دسترسی به ویژگی‌ها است. تفاوت به دلیل عدم وجود جستجوی زمان اجرا و توزیع پویا است. 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 برای ذخیره و ارسال KeyPathهای به‌دست‌آمده به صورت ایستا استفاده کرد. برای دسترسی پویا به ویژگی‌ها از طریق رشته، از value(forKey:) با Objective-C runtime استفاده کنید.

خلاصه

  • KeyPath — مسیر تایپ‌شده به ویژگی، ارائه‌شده به عنوان نوع مقدار درجه اول
  • سلسله‌مراتب KeyPath شامل WritableKeyPath و ReferenceWritableKeyPath برای سطوح مختلف دسترسی است
  • Combine و SwiftUI به طور فعال از KeyPath برای اتصالات واکنشی و بایندینگ استفاده می‌کنند
  • مرتب‌سازی و فیلتر از طریق KeyPath خوانایی و امنیت نوع کد را افزایش می‌دهد
  • AnyKeyPath — نوع پاک‌شده برای ذخیره KeyPathهای ناهمگن در مجموعه‌ها
  • عملکرد KeyPath 10-20 برابر بیشتر از value(forKey:) است

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید