Generics في Swift: لماذا هي ضرورية وكيفية استخدامها عمليًا
Generics (البرمجة العامة) هي واحدة من أقوى وأهم ميزات لغة Swift. فهي تسمح بكتابة كود مرن، آمن من حيث النوع، وقابل لإعادة الاستخدام، مع تجنب التكرار. إذا كنت قد استخدمت يومًا Array<Int> أو Dictionary<String, Any> أو Optional<T>، فقد تعاملت بالفعل مع generics. في هذه المقالة، سنشرح لماذا هي ضرورية، وكيفية تطبيقها بشكل صحيح، وسنستعرض أمثلة عملية لتطوير iOS/macOS.
لماذا نحتاج Generics في Swift؟
المشكلة الرئيسية التي تحلها generics هي الحاجة إلى كتابة نفس الكود لأنواع بيانات مختلفة. بدونها، سيتعين عليك إنشاء دوال منفصلة للعمل مع Int و String و Double والهياكل المخصصة. وهذا يؤدي إلى تكرار الكود، والأخطاء، وصعوبة الصيانة.
المزايا الرئيسية لـ generics:
- أمان النوع: يتحقق المترجم من الأنواع في وقت الترجمة، مما يمنع أخطاء وقت التشغيل.
- إعادة استخدام الكود: دالة أو نوع واحد يعمل مع أي نوع بيانات.
- الأداء: يتم تنفيذ generics في Swift من خلال التخصص — حيث يقوم المترجم بإنشاء كود محسّن لكل نوع محدد، وهو ما يعادل كتابة الكود المكرر يدويًا.
- الوضوح والتجريد: يصبح الكود أكثر إيجازًا وتعبيرًا.
لنأخذ مثالًا بسيطًا. لنفترض أننا بحاجة إلى دالة لتبادل قيم متغيرين. بدون 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}// ... وهكذا لكل نوعمع generics، يصبح الحل عالميًا:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10var y = 20swapValues(&x, &y) // يعمل مع Int
var str1 = "Hello"var str2 = "World"swapValues(&str1, &str2) // يعمل مع Stringكما ترى، <T> هو عنصر نائب (placeholder) للنوع. يقوم المترجم تلقائيًا باستنتاج النوع المحدد عند استدعاء الدالة.
الدوال والأنواع العامة (Generic)
يمكن استخدام Generics ليس فقط مع الدوال، ولكن أيضًا مع الأنواع المخصصة: الهياكل، والفئات، والتعدادات.
الدوال العامة (Generic functions)
بالإضافة إلى العنصر النائب البسيط، يمكن فرض قيود على النوع. على سبيل المثال، دالة تعمل فقط مع الأنواع التي تطبق بروتوكول Equatable:
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, item) in array.enumerated() { if item == value { return index } } return nil}
let numbers = [1, 5, 3, 12]if let index = findIndex(of: 5, in: numbers) { print("تم العثور على الفهرس: \\(index)") // سيطبع: تم العثور على الفهرس: 1}القيود <T: Equatable> تضمن أن العامل == محدد للنوع T.
الأنواع العامة (Generic types) (الهياكل والفئات)
لننشئ مكدسًا بسيطًا — بنية بيانات تعمل وفق مبدأ LIFO (Last In, First Out):
struct Stack<Element> { private var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element? { return items.popLast() } var top: Element? { return items.last }}
var intStack = Stack<Int>()intStack.push(10)intStack.push(20)print(intStack.pop() ?? 0) // 20
var str