معالجة الأخطاء في Swift: try/catch وما بعدها
يتطلب التطوير لنظامي iOS وmacOS إدارة موثوقة للأخطاء. توفر Swift آلية قوية وأنيقة لمعالجة الأعطال، تعتمد على الكلمات المفتاحية throws وtry وcatch وdo. في هذا المقال، سنشرح كيفية معالجة الأخطاء بشكل صحيح، وكتابة كود قابل للقراءة، وتجنب المزالق الشائعة.
1. الأساسيات: بروتوكول Error والكلمة المفتاحية throws
يجب أن يتوافق أي خطأ في Swift مع بروتوكول Error. عادةً ما يتم استخدام التعدادات (enum) لهذا الغرض:
enum NetworkError: Error { case badURL case noData case decodingFailed(String)}الدالة التي يمكنها إلقاء خطأ تُعلَم بالكلمة المفتاحية throws:
func fetchUser(from urlString: String) throws -> User { guard let url = URL(string: urlString) else { throw NetworkError.badURL } // ... منطق الطلب}الآن، عند استدعاء هذه الدالة، سيلزمك المترجم بمعالجة الخطأ المحتمل.
2. بناء do-catch: اعتراض الأخطاء
الطريقة الأكثر شيوعاً للمعالجة هي كتلة do-catch. تضع الكود الذي قد يكون خطيراً داخل do، وفي catch تعالج العطل:
do { let user = try fetchUser(from: "https://api.example.com/user") print("User loaded: \\(user.name)")} catch NetworkError.badURL { print("رابط URL غير صحيح")} catch NetworkError.noData { print("الخادم لم يُرجع بيانات")} catch NetworkError.decodingFailed(let message) { print("خطأ في فك الترميز: \\(message)")} catch { print("خطأ غير معروف: \\(error.localizedDescription)")}هام: كتلة catch الأخيرة بدون نوع محدد تلتقط جميع الأخطاء المتبقية — يجب إضافتها دائماً.
2.1. مطابقة الأنماط في catch
يمكنك استخدام شروط أكثر تعقيداً:
do { let result = try riskyOperation()} catch let error as NetworkError where error == .badURL { print("مشكلة في الرابط URL")} catch { print(error)}3. البدائل: try? و try!
ليست هناك حاجة دائماً للمعالجة الكاملة. تقدم Swift طريقتين مبسطتين:
3.1. try? — قيمة اختيارية
إذا لم يكن الخطأ حرجاً، يمكن تحويل النتيجة إلى قيمة اختيارية (optional). عند العطل، ستعيد الدالة nil:
let user = try? fetchUser(from: "https://example.com/user")// user من النوع User? — يكون nil عند الخطأمثالي للحالات التي يمكن فيها تجاهل الخطأ ببساطة (مثل تحميل الصورة الرمزية).
3.2. try! — التنفيذ الإجباري
استخدم try! فقط عندما تكون متأكداً تماماً من عدم وجود خطأ. وإلا — سيحدث تعطل التطبيق:
let user = try! fetchUser(from: "https://valid-url.com/user")// إذا كان الرابط غير صالح — سيتعطل التطبيقاستخدم try! في الاختبارات أو في الحالات التي يكون فيها العطل مستحيلاً حسب منطق البرنامج.
4. تقنيات متقدمة: rethrows و deferred
4.1. rethrows للدوال عالية المستوى
إذا كانت الدالة الخاصة بك تستقبل إغلاقاً (closure) يمكنه إلقاء خطأ، استخدم rethrows. هذا يسمح بعدم تعليم الدالة نفسها كـ throws إذا لم يلقِ الإغلاق خطأ:
func process(_ closure: () throws -> Void) rethrows { try closure()}
// استدعاء بدون إغلاق throws — لا يتطلب do-catchprocess { print("بدون أخطاء") }
// استدعاء مع إغلاق throws — يتطلب معالجةtry process { throw NetworkError.noData }4.2. deferred: التنظيف المضمون
كتلة defer تُنفذ عند أي إنهاء للدالة (نجاح أو خطأ). مناسبة تماماً لإغلاق الملفات أو تحرير الموارد.