معالجة الأخطاء في Swift: try/catch وأكثر (مستوى متقدم)
\n\nمعالجة الأخطاء هي أحد الموضوعات الأساسية في التطوير باستخدام Swift. إذا كنت بالفعل على دراية بـ do-catch و try? الأساسيين، فقد حان الوقت للانتقال إلى المستوى التالي. في هذه المقالة، سنستعرض التقنيات المتقدمة: العمل مع بروتوكول Error، ونوع Result، والكلمة المفتاحية rethrows، بالإضافة إلى كيفية تصميم تسلسل هرمي للأخطاء بشكل صحيح في مشاريع iOS/macOS الحقيقية.
غوص عميق في بروتوكول Error والأخطاء المخصصة
\n\nفي Swift، يجب أن يتوافق أي خطأ مع بروتوكول Error. ومع ذلك، لجعل الكود أكثر قابلية للقراءة والصيانة، يُنصح بإنشاء تعدادات (enum) خاصة بك تحتوي على حالات متداخلة وقيم مرتبطة.
enum NetworkError: Error {\n case invalidURL\n case noData\n case decodingFailed(description: String)\n case serverError(statusCode: Int)\n case timeout(seconds: Double)\n}\n\n// مثال على الاستخدام\nfunc fetchUser(id: Int) throws -> User {\n guard id > 0 else {\n throw NetworkError.invalidURL\n }\n // ... منطق الطلب\n if someCondition {\n throw NetworkError.serverError(statusCode: 500)\n }\n return User()\n}\n\nيسمح هذا الأسلوب بنقل سياق إضافي: رمز الخطأ، الوصف، أو وقت الانتظار. هذا مفيد بشكل خاص في تسجيل الأخطاء وتصحيحها.
\n\nResult: بديل لـ do-catch للكود غير المتزامن
\n\nنوع Result<Success, Failure> هو أداة قوية جاءت من البرمجة الوظيفية. إنه مثالي لواجهات برمجة التطبيقات القائمة على الاستدعاءات (callback-based API)، والطلبات الشبكية، والعمليات غير المتزامنة.
enum DataError: Error {\n case corrupted\n case notFound\n}\n\nfunc loadImage(from url: URL, completion: @escaping (Result<UIImage, DataError>) -> Void) {\n DispatchQueue.global().async {\n // محاكاة التحميل\n if arc4random_uniform(2) == 0 {\n completion(.success(UIImage()))\n } else {\n completion(.failure(.notFound))\n }\n }\n}\n\n// الاستخدام\nloadImage(from: someURL) { result in\n switch result {\n case .success(let image):\n print("تم تحميل الصورة: \\(image)")\n case .failure(let error):\n print("خطأ: \\(error.localizedDescription)")\n }\n}\n\nيجعل Result الكود أكثر قابلية للتنبؤ ويخلصك من الحاجة إلى معالجة القيم الاختيارية مع nil. بالإضافة إلى ذلك، يمكنك استخدام .map و .flatMap لسلاسل التحويلات.
Rethrows: عندما "تمرر" الدالة الأخطاء
\n\nالكلمة المفتاحية rethrows هي واحدة من أكثر إمكانيات Swift التي لا تحظى بالتقدير الكافي. تسمح لك بكتابة دالة لا ترمي الأخطاء بنفسها، ولكنها "تمررها" إذا كان الإغلاق (closure) المُمرر هو throws.
func performOperation<T>(on array: [T], operation: (T) throws -> Bool) rethrows -> [T] {\n var result: [T] = []\n for element in array {\n if try operation(element) {\n result.append(element)\n }\n }\n return result\n}\n\n// بدون أخطاء (لا حاجة لـ try)\nlet numbers = [1, 2, 3, 4, 5]\nlet evenNumbers = performOperation(on: numbers) { $0.isMultiple(of: 2) }\nprint(evenNumbers) // [2, 4]\n\n// مع خطأ (بحاجة لـ try)\nenum ValidationError: Error { case negative }\nlet filtered = try performOperation(on: numbers) { value in\n guard value > 0 else { throw ValidationError.negative }\n return value % 2 == 0\n}\n\nيجعل rethrows واجهة برمجة التطبيقات مرنة: يمكن للمستخدم تمرير إغلاق عادي (بدون try) أو إغلاق مع throws (عندها سيكون try مطلوبًا). يُستخدم هذا غالبًا في المكتبة القياسية لـ Swift - على سبيل المثال، في map و filter و for