مقدمة: لماذا قد ينهار كودك في أي لحظة؟
\nكل مطور Swift واجه مرة على الأقل هذا الخطأ الشبح: Fatal error: Index out of range. يظهر فجأة، يدمر التطبيق، ويرى المستخدم شاشة بيضاء فقط. قد يبدو الأمر بسيطًا، أخذ عنصر من مصفوفة باستخدام الفهرس؟ لكن في المشاريع الحقيقية - خاصة عند العمل مع بيانات الشبكة، تحليل JSON، أو إدخال المستخدم - يمكن للفهرس أن يتجاوز الحدود بسهولة.
\nفي هذه المقالة، سنشرح كيف تنسى مشكلة index out of range إلى الأبد وتجعل كودك ليس آمنًا فحسب، بل أنيقًا أيضًا. سنتحدث عن safe array lookup في Swift، ونستخدم optional bindings (if let و guard let)، ونتعلم كتابة إضافاتنا الخاصة، ونرى كيف يفعل المحترفون ذلك في بيئة الإنتاج.
\n\nالمشكلة: الوصول القياسي إلى المصفوفة - لعبة الروليت الروسية
\nلنلق نظرة على موقف نموذجي. لديك مصفوفة من السلاسل النصية:
\nlet fruits = ["Яблоко", "Банан", "Апельсин"]\nlet index = 3\nprint(fruits[index]) // 💥 Fatal error: Index out of range\nالكود سينهار فورًا. Swift هي لغة ذات كتابة آمنة، لكن الـ subscript القياسي ([]) لا يتحقق من الحدود. إنه ببساطة يأخذ العنصر من عنوان الذاكرة، وإذا كان الفهرس غير صحيح - ينهار التطبيق. هذا إرث من C و Objective-C الذي نريد التخلص منه.
الحل رقم 1: Optional Binding (if let) - الخطوة الأولى نحو الأمان
\nالطريقة الأكثر وضوحًا هي التحقق من الفهرس يدويًا قبل الوصول. لكن Swift يقدم طريقة أكثر اصطلاحية: استخدام optional binding مع التحقق من النطاق.
\nهذا ما يبدو عليه:
\nlet fruits = ["Яблоко", "Банан", "Апельсин"]\nlet index = 2\n\nif index < fruits.count {\n let fruit = fruits[index]\n print("Мы получили: \\(fruit)")\n} else {\n print("Индекс \\(index) вне диапазона")\n}\nهذا يعمل، لكنه يبدو ضخمًا. ماذا لو أردنا فعل ذلك في سطر واحد والحصول على قيمة اختيارية؟ هنا يأتي دور guard:
\nfunc safeFruit(at index: Int) -> String? {\n guard index >= 0, index < fruits.count else { return nil }\n return fruits[index]\n}\n\nif let fruit = safeFruit(at: 3) {\n print("Ура, мы получили фрукт: \\(fruit)")\n} else {\n print("Фрукта по такому индексу нет")\n}\nهذا أفضل! الدالة تُرجع Optional<String>، ونحن نتعامل مع nil بهدوء. لا انهيارات. لكن كتابة دالة كهذه لكل مصفوفة - متعة مشكوك فيها.
الحل رقم 2: الإضافة (Extension) - سحر البروتوكولات
\nالقوة الحقيقية لـ Swift تكمن في الإضافات. يمكننا إضافة subscript آمن لأي مصفوفة مرة واحدة واستخدامه في كل مكان. إليك كيف يتم ذلك:
\nextension Collection {\n /// يعيد بأمان العنصر حسب الفهرس إذا كان موجودًا.\n /// - Parameter index: فهرس العنصر.\n /// - Returns: عنصر اختياري.\n subscript(safe index: Index) -> Element? {\n return indices.contains(index) ? self[index] : nil\n }\n}\nالآن يمكننا الكتابة هكذا:
\nlet numbers = [10, 20, 30, 40, 50]\n\nif let first = numbers[safe: 0] {\n print("Первый элемент: \\(first)") // 10\n}\n\nif let last = numbers[safe: 4] {\n print("Последний элемент: \\(last)") // 50\n}\n\nif let nonExistent = numbers[safe: 10] {\n print("Это никогда не выведется")\n} else {\n print("Элемента с индексом 10 не существует")\n}\nلاحظ: لقد قمنا بتوسيع بروتوكول Collection، وليس النوع المحدد Array. هذا يعني أن الـ subscript الآمن الخاص بنا يعمل مع جميع المجموعات: المصفوفات، السلاسل النصية، DictionaryKeys، Set، وحتى الأنواع المخصصة التي تتوافق مع Collection.
لماذا يعمل هذا؟
\n- \n