مقدمة: لماذا لا غنى عن Generics في Swift؟
تخيل أنك بحاجة لكتابة دالة تجمع رقمين. ثم دالة لجمع سلسلتين نصيتين. ثم بعد ذلك للمصفوفات. بدون البرمجة العامة (Generics) سيتعين عليك إنشاء نسخة منفصلة من الكود لكل نوع بيانات. هذا لا يزيد حجم الكود فحسب، بل يعقد صيانته أيضًا.
Generics (الجينيريكس) في Swift هي أداة قوية تسمح لك بكتابة كود مرن وقابل لإعادة الاستخدام وآمن من حيث النوع. وهي تشكل الأساس لهياكل شائعة مثل Array وDictionary وOptional. في هذه المقالة، سنشرح سبب الحاجة إلى Generics، وكيفية استخدامها عمليًا، ولماذا يجب على كل مطور مبتدئ في iOS/macOS إتقان هذا الموضوع.
ما هي Generics بكلمات بسيطة؟
Generics (التعميمات) هي طريقة لكتابة كود يعمل مع أي نوع بيانات، دون التضحية بالكتابة الصارمة في Swift. بدلاً من تحديد نوع معين (مثل Int أو String)، تستخدم عنصرًا نائبًا (غالبًا الحرف T)، والذي يتم استبداله بنوع حقيقي عند استدعاء الدالة أو إنشاء الهيكل.
المشكلة بدون Generics
لنفترض أنك تكتب دالة لتبادل قيم متغيرين. بدون Generics، سيتعين عليك الكتابة بهذا الشكل:
func swapInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}
func swapStrings(_ a: inout String, _ b: inout String) { let temp = a a = b b = temp}هذا تكرار واضح. إذا احتجت لتبادل Double أو أنواع مخصصة، سيزداد عدد الدوال.
الحل مع Generics
دالة واحدة شاملة ستحل محل جميع الدوال السابقة:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
// الاستخدام:var x = 10var y = 20swapValues(&x, &y) // x = 20, y = 10
var firstName = "آنا"var lastName = "إيفانوفا"swapValues(&firstName, &lastName) // firstName = "إيفانوفا"الأقواس الزاوية <T> تعلن عن العنصر النائب للنوع. يحدد المترجم تلقائيًا أن T هو Int في الحالة الأولى وString في الحالة الثانية. يصبح الكود موجزًا وآمنًا.
كيفية استخدام Generics: السيناريوهات الرئيسية
تُستخدم Generics في Swift في الدوال والهياكل والفئات والتعدادات والبروتوكولات. دعنا نستعرض الحالات الأكثر شيوعًا.
1. الدوال العامة
لقد رأينا بالفعل مثال swapValues. ولكن يمكننا الذهاب أبعد من ذلك - إنشاء دالة تجد فهرس عنصر في مصفوفة:
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == value { return index } } return nil}
let numbers = [1, 5, 9, 12]if let index = findIndex(of: 9, in: numbers) { print("الفهرس: \\(index)") // الفهرس: 2}لاحظ <T: Equatable>. هذا قيد نوع (type constraint). نحن نقول: "يجب أن يتوافق T مع بروتوكول Equatable حتى نتمكن من استخدام عامل التشغيل ==". بدون هذا القيد، لن يتم تجميع الكود.
2. الأنواع العامة (الهياكل والفئات)
لننشئ هيكلًا بسيطًا "مكدس" (LIFO) يمكنه تخزين أي نوع:
struct Stack<Element> { private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { return items.popLast() }
func peek() -> Element? { return items.last }}
// الاستخدام:var intStack = Stack<Int>()intStack.push(1)intStack.push(2)print(intStack.pop