استخدام KeyPaths في المجموعات
رتّب المجموعات وطبّق map عليها باستخدام مسار المفتاح.
استخدام KeyPaths في المجموعات درس مجاني في Swift Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Swift Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Swift Academy 4 دروس في المجموع.
تتألق مسارات المفاتيح في المجموعات
غالبًا ما تقرأ عمليات المجموعات مثل map وsorted وfilter خاصية واحدة فقط من كل عنصر. تتيح لكم مسارات المفاتيح التعبير عن هذه النية من دون كتابة closure.
تقبل Swift مسار مفتاح في أي موضع يُتوقَّع فيه وجود دالة ذات وسيط واحد تُرجع قيمة، لأن مسار المفتاح يمكن استخدامه كدالة من Root إلى Value.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let names = people.map(\.name)
print(names)استخدام map بالصيغة المختصرة لمسار المفتاح
يعادل تمرير map(\.property) كتابة closure تُرجع تلك الخاصية، لكنه أقصر وأكثر وضوحًا. تتيح الشرطة المائلة العكسية والنقطة في البداية للمترجم استنتاج نوع العنصر.
قارنوا بين الصيغتين أدناه؛ فهما تنتجان النتيجة نفسها.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let closureForm = people.map { $0.age }
let keyPathForm = people.map(\.age)
print(closureForm)
print(keyPathForm)استخدام sorted(by:) مع مقارنة عبر مسار مفتاح
للفرز حسب خاصية، ما زلتم تكتبون closure صغيرة للمقارنة، لكن يمكنكم قراءة الخاصية من خلال مسار مفتاح داخلها لزيادة الوضوح.
نفرز الأشخاص هنا من الأصغر سنًا إلى الأكبر سنًا بمقارنة قيم age الخاصة بهم.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let path = \Person.age
let sorted = people.sorted { $0[keyPath: path] < $1[keyPath: path] }
print(sorted.map(\.name))مساعد فرز قابل لإعادة الاستخدام
يمكنكم كتابة دالة مساعدة عامة تفرز أي مصفوفة حسب أي خاصية قابلة للمقارنة، مع تمرير الخاصية كوسيط لمسار مفتاح. يحوّل ذلك عملية الفرز حسب حقل إلى تعبير من سطر واحد في مختلف مواضع قاعدة الشيفرة.
يضمن القيد T: Comparable إمكانية ترتيب الخاصية.
func sorted<E, T: Comparable>(_ array: [E], by path: KeyPath<E, T>) -> [E] {
array.sorted { $0[keyPath: path] < $1[keyPath: path] }
}
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
print(sorted(people, by: \.age).map(\.name))التصفية عبر مسار مفتاح
تحتاجون في filter إلى predicate تُرجع قيمة من النوع Bool، لذلك لا يعمل مسار خاصية مجرد مباشرةً إلا عندما تكون الخاصية من النوع Bool أصلًا.
يحتوي كل عنصر مهمة هنا على علامة isDone، ونقوم بالتصفية وفقًا لها باستخدام الصيغة المختصرة لمسار المفتاح.
struct Task {
let title: String
let isDone: Bool
}
let tasks = [Task(title: "A", isDone: true), Task(title: "B", isDone: false)]
let finished = tasks.filter(\.isDone)
print(finished.map(\.title))التصفية حسب خاصية ليست من النوع Bool
عند التصفية حسب خاصية ليست من النوع Bool، اقرأوها من خلال مسار مفتاح داخل closure ثم قارنوها. ويمكن لمساعد عام استيعاب هذا النمط لأي خاصية قابلة للمساواة.
نُبقي هنا على الأشخاص الذين تطابق مدينتهم مدينة مستهدفة فقط.
struct Person { let name: String; let city: String }
func filter<E, T: Equatable>(_ array: [E], _ path: KeyPath<E, T>, equals value: T) -> [E] {
array.filter { $0[keyPath: path] == value }
}
let people = [Person(name: "Ana", city: "Oslo"), Person(name: "Ben", city: "Rome")]
print(filter(people, \.city, equals: "Oslo").map(\.name))استخدام max وmin حسب مسار مفتاح
تأخذ الطريقتان max(by:) وmin(by:) closure للمقارنة، تمامًا مثل sorted. اقرأوا الخاصية من خلال مسار مفتاح للعثور على العنصر ذي القيمة الأكبر أو الأصغر.
نعثر هنا على أكبر الأشخاص سنًا.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25), Person(name: "Cy", age: 41)]
let path = \Person.age
if let oldest = people.max(by: { $0[keyPath: path] < $1[keyPath: path] }) {
print(oldest.name)
}جمع خاصية باستخدام reduce
يمكنكم دمج map(\.property) مع reduce لحساب مجموع حقل رقمي. حوّلوا كل عنصر أولًا إلى الرقم المطلوب، ثم اجمعوا الأرقام.
نجمع هنا جميع الأعمار في مسار معالجة واحد.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let totalAge = people.map(\.age).reduce(0, +)
print(totalAge)التجميع حسب مسار مفتاح
تجمع Dictionary(grouping:by:) العناصر في مجموعات حسب ناتج closure. اقرأوا خاصية من خلال مسار مفتاح للتجميع حسب ذلك الحقل.
يُجمَّع الأشخاص هنا حسب المدينة في قاموس من المصفوفات.
struct Person { let name: String; let city: String }
let people = [
Person(name: "Ana", city: "Oslo"),
Person(name: "Ben", city: "Rome"),
Person(name: "Cy", city: "Oslo")
]
let path = \Person.city
let byCity = Dictionary(grouping: people, by: { $0[keyPath: path] })
print(byCity["Oslo"]?.map(\.name) ?? [])الفرز حسب عدة حقول
للفرز حسب حقل وكسر حالات التعادل باستخدام حقل آخر، قارنوا مسار المفتاح الأساسي أولًا، ثم استخدموا المسار الثانوي عندما تتساوى القيم الأساسية.
نفرز هنا حسب العمر، ثم حسب الاسم عند تساوي الأعمار.
struct Person { let name: String; let age: Int }
let people = [
Person(name: "Zoe", age: 30),
Person(name: "Ana", age: 30),
Person(name: "Ben", age: 25)
]
let sorted = people.sorted {
if $0[keyPath: \Person.age] != $1[keyPath: \Person.age] {
return $0.age < $1.age
}
return $0.name < $1.name
}
print(sorted.map(\.name))لماذا تتفوق مسارات المفاتيح على closures هنا
يؤدي استخدام مسارات المفاتيح في استدعاءات المجموعات إلى تقليل الشيفرة المتكررة، وإزالة احتمال وجود خطأ مطبعي في جسم closure، وجعل النية واضحة: أنتم تستخرجون حقلًا واحدًا.
ما تزال closure هي الأداة المناسبة للتحويلات المعقدة، لكن عند قراءة خاصية عادية تكون \.property هي الخيار الأوضح.
struct Product { let name: String; let price: Double }
let products = [Product(name: "Pen", price: 2), Product(name: "Pad", price: 5)]
let names = products.map(\.name)
let prices = products.map(\.price)
print(names)
print(prices)تحقق سريع: مسار مفتاح في map
اختاروا الصيغة المختصرة لمسار المفتاح لاستخراج خاصية واحدة من كل عنصر.
مراجعة: مسارات المفاتيح في المجموعات
رأيتم كيف تجعل مسارات المفاتيح شيفرة المجموعات موجزة:
- تستخرج
map(\.property)حقلًا واحدًا من دون closure. - تعمل
filter(\.boolProperty)مباشرةً مع حقول Bool؛ أما الحقول الأخرى فاستخدموا معها closure تحتوي على[keyPath:]. - تقرأ عمليات الفرز و
maxوminالخاصية من خلال مسار مفتاح داخل closures المقارنة الخاصة بها. - تحوّل المساعدات العامة التي تأخذ
KeyPathعملية الفرز والتصفية حسب حقل إلى تعبيرات من سطر واحد قابلة لإعادة الاستخدام.
ستتعلمون بعد ذلك مسارات المفاتيح القابلة للكتابة، التي تتيح لكم تعيين القيم، وليس قراءتها فقط.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let youngestFirst = people.sorted { $0[keyPath: \Person.age] < $1[keyPath: \Person.age] }
print(youngestFirst.map(\.name))الأسئلة الشائعة
هل درس «استخدام KeyPaths في المجموعات» مجاني؟
نعم — نص درس «استخدام KeyPaths في المجموعات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Swift Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Swift Academy 4 دروس في المجموع.
ماذا ستتعلم في «استخدام KeyPaths في المجموعات»؟
رتّب المجموعات وطبّق map عليها باستخدام مسار المفتاح. تتمرن على Swift Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Swift Academy؟
لا تُشترط خبرة سابقة. Swift Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «استخدام KeyPaths في المجموعات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Swift Academy هذا؟
نعم. كل درس في Swift Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- أساسيات KeyPath
- استخدام KeyPaths في المجموعات
- مسارات المفاتيح القابلة للكتابة والمرجعية
- @dynamicMemberLookup