معالجة الأخطاء في Swift: try/catch وغيرها
في تطوير iOS و macOS الحديث، تعد موثوقية التطبيق أحد المؤشرات الرئيسية للجودة. تتيح المعالجة الصحيحة للأخطاء في Swift ليس فقط تجنب الأعطال غير المتوقعة، بل أيضًا جعل الكود أكثر قابلية للقراءة والتوقع. في هذه المقالة، سنتناول ليس فقط الآلية التقليدية try/catch، بل أيضًا الأساليب البديلة، بما في ذلك النوع Result والعامل defer والأنماط المتقدمة.
الأساسيات: بروتوكول Error والكلمة المفتاحية throws
في Swift، يتم تمثيل الأخطاء بأنواع تتوافق مع بروتوكول Error. غالبًا ما يتم استخدام التعدادات (enum) لهذا الغرض. لكي تتمكن الدالة من إلقاء خطأ، يتم استخدام الكلمة المفتاحية throws في تعريفها.
enum NetworkError: Error { case noInternet case serverError(code: Int) case timeout}
func fetchData(from url: String) throws -> Data { guard url.hasPrefix("https") else { throw NetworkError.serverError(code: 400) } // ... محاكاة التحميل return Data()}لاحظ: إذا كانت الدالة موسومة بـ throws، فإن الكود المستدعي ملزم بمعالجة الخطأ المحتمل باستخدام try أو try? أو try!.
تركيبة do-catch: الطريقة التقليدية
الطريقة الأكثر شيوعًا للمعالجة هي كتلة do-catch. تضع الكود الذي قد يكون خطيرًا داخل do، وفي catch تصف ما يجب فعله مع كل خطأ محدد.
do { let data = try fetchData(from: "http://example.com") print("تم استلام البيانات: \\(data)")} catch NetworkError.noInternet { print("لا يوجد اتصال بالإنترنت")} catch NetworkError.serverError(let code) { print("خطأ في الخادم برمز: \\(code)")} catch { print("خطأ غير معروف: \\(error.localizedDescription)")}هام: كتلة catch الأخيرة بدون نمط تلتقط جميع الأخطاء المتبقية. هذه ممارسة جيدة لضمان عدم تفويت استثناءات غير متوقعة.
try? و try!: بدائل سريعة
إذا لم يكن نوع الخطأ مهمًا بالنسبة لك، وتحتاج فقط إلى معرفة ما إذا كانت الدالة قد نُفذت بنجاح، استخدم try?. في حالة حدوث خطأ، ستكون النتيجة nil.
if let data = try? fetchData(from: "https://valid.com") { print("نجاح: \\(data)")} else { print("خطأ أثناء التحميل")}العامل try! هو "قنبلة موقوتة". فهو يثبط معالجة الأخطاء ويؤدي إلى تعطل التطبيق إذا حدث خطأ. استخدمه فقط في الحالات التي تكون فيها متأكدًا بنسبة 100% من عدم وجود خطأ (على سبيل المثال، عند تحميل مورد مدمج).
let data = try! fetchData(from: "https://trusted-source.com")// إذا حدث خطأ - سيتعطل التطبيقالنوع Result: النهج الوظيفي
بدءًا من Swift 5، تمت إضافة النوع Result<Success, Failure> إلى المكتبة القياسية. وهو إغلاق يمكن أن يحتوي إما على قيمة ناجحة أو خطأ. هذا النهج مناسب بشكل خاص في الكود غير المتزامن وفي دوال رد الاتصال (completion handlers).
func fetchDataResult(from url: String) -> Result<Data, NetworkError> { guard url.hasPrefix("https") else { return .failure(.serverError(code: 400)) } return .success(Data())}
// الاستخدامlet result = fetchDataResult(from: "http://bad.com")switch result {case .success(let data): print("البيانات: \\(data)")case .failure(let error): print("الخطأ: \\(error)")}ميزة Result هي أنك لا تنسى معالجة الخطأ، لأن المترجم يجبرك على ذلك.