defer — ما هو، بناء الجملة وترتيب التنفيذ

المؤلف: IT Sectr نُشر: 2026-06-20 وقت القراءة: 8 دق

defer هي بنية تحكم في التدفق في Swift تقوم بجدولة تنفيذ كتلة من التعليمات البرمجية عند الخروج من النطاق الحالي. يتم تنفيذ كتلة defer بغض النظر عن كيفية انتهاء النطاق — return أو break أو throw أو fatalError أو الإنهاء العادي. وفقًا لدليل لغة Swift (2025)، عند وجود عدة defer في نطاق واحد، يتم تنفيذها بترتيب عكسي للإعلان — آخر defer معلن يتم تنفيذه أولاً (LIFO). هذا يجعل defer لا غنى عنه للتنظيف المضمون للموارد: إغلاق واصفات الملفات، تحرير الأقفال، تحرير المؤشرات المؤقتة دون خطر تفويت التنظيف عند الخروج المبكر.

النقاط الرئيسية

  • defer — كتلة من التعليمات البرمجية يتم تنفيذها عند الخروج من نطاق، بغض النظر عن سبب الخروج (return, throw, break)
  • ترتيب LIFO: يتم تنفيذ عدة defer من الأسفل إلى الأعلى — آخر defer معلن يتم تنفيذه أولاً
  • تنظيف الموارد — حالة الاستخدام الرئيسية: إغلاق الملفات، تحرير الأقفال، إكمال الرسوم المتحركة
  • تقييم المتغيرات: defer يرى المتغيرات في لحظة الخروج، وليس في لحظة الإعلان
  • لا يستبدل do-catch لمعالجة الأخطاء — defer مسؤول عن التنظيف، وليس عن التحكم في التدفق

ما هو defer؟

defer هي بنية تحكم في التدفق في Swift، تم تقديمها في Swift 2.0 (2015)، والتي تؤجل تنفيذ كتلتها حتى ينتهي النطاق الحالي. الميزة الرئيسية: defer يضمن تنفيذ جسمه بغض النظر عن كيفية انتهاء النطاق — بنجاح (return)، بخطأ (throw)، مبكرًا (break, continue) أو بشكل قاتل (fatalError, precondition).

من الناحية التركيبية، يكتب defer كـ defer { /* كود */ } ويمكن وضعه في أي مكان داخل النطاق. يضمن مترجم Swift أن التعليمات البرمجية داخل defer سيتم تنفيذها حتى في حالة حدوث استثناء أو return بين إعلان defer ونهاية النطاق. هذا يميز defer بشكل جوهري عن التعليمات البرمجية العادية الموضوعة في نهاية الدالة، والتي قد يتم تخطيها عند الخروج المبكر.

وفقًا لمقال بقلم كريس لاتنر (مبتكر Swift، 2015)، استلهم defer من بنيات مماثلة في لغات أخرى — defer في Go، finally في Java/Python، scope guard في C++ — ولكن مع فرق مهم: في Swift، يتم تنفيذ defer في نهاية النطاق، وليس مباشرة بعد كتلة try-catch. هذا يوفر سلوكًا أكثر قابلية للتنبؤ للتنظيف في الدوال ذات نقاط الخروج المتعددة.

استخدم defer للإدارة المتماثلة للموارد: فتح ملف → defer { close }، الحصول على قفل → defer { unlock }. هذا النمط يضمن أن تحرير الموارد لن يتم تفويته أبدًا تحت أي ظرف.

ترتيب تنفيذ عدة defer

عند إعلان عدة defer في نفس النطاق، يتم تنفيذها بترتيب عكسي للإعلان (LIFO — Last In, First Out). هذا يعني أن آخر defer معلن يتم تنفيذه أولاً، وأول defer يتم تنفيذه آخرًا:

swift
func exampleDeferOrder() {
    defer { print("defer الأول") }
    defer { print("defer الثاني") }
    defer { print("defer الثالث") }

    print("جسم الدالة")
}
// الإخراج:
// جسم الدالة
// defer الثالث
// defer الثاني
// defer الأول

ترتيب LIFO مهم للإدارة الصحيحة للموارد المتداخلة. إذا تم فتح الملف A أولاً، ثم الملف B، يجب تحريرهما بترتيب عكسي: أولاً B، ثم A. مع defer، يحدث هذا تلقائيًا — قم بتعريف defer مباشرة بعد فتح كل مورد، وسيكون ترتيب التنظيف صحيحًا بغض النظر عن عدد نقاط الخروج في الدالة.

وفقًا لـ Swift by Sundell (2024)، هذه الميزة تجعل defer مثاليًا للأقفال المتداخلة والمعاملات: الحصول على قفل → defer { unlock } → الحصول على التالي → defer { unlock }. LIFO يضمن تحرير الأقفال بترتيب عكسي للحصول عليها، مما يمنع حالات الجمود (deadlocks).

Defer لتنظيف الموارد

حالة الاستخدام الرئيسية لـ defer هي التنظيف المضمون للموارد. لنفكر في العمل مع نظام الملفات. فتح ملف عبر FileHandle يتطلب إغلاقًا صريحًا — defer يضمن استدعاء close في أي سيناريو:

swift
func readFile(path: String) throws -> String {
    let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer { try? handle.close() }

    let data = try handle.readToEnd()
    guard let data else { throw FileError.empty() }

    return String(data: data, encoding: .utf8) ?? ""
    // سيتم استدعاء handle.close() حتى في حالة throw أو return
}

سيناريو نموذجي آخر هو رسوم متحركة للواجهة مع مؤشر تحميل. قبل بدء التحميل، قم بتعيين المؤشر isLoading = true، ويقوم defer بإعادته إلى false عند الخروج من الدالة، بغض النظر عن نجاح أو فشل الطلب. هذا يمنع بقاء المؤشر على true بسبب خطأ غير معالج، مما قد يحجب الواجهة إلى الأبد.

وفقًا لمدونة Bitbucket Engineering (2024)، يُستخدم defer أيضًا للتنميط: سجل الوقت في بداية الدالة، وفي defer — احسب وأظهر الفرق. هذا يعطي قياسات أداء دقيقة لجميع مسارات التنفيذ، بما في ذلك المسارات الخاطئة.

Defer ومعالجة الأخطاء

defer يعمل بفعالية مع دوال throws. عندما يمكن لدالة أن ترمي خطأ في أي مرحلة، يضمن defer التنظيف دون تكرار الكود في كل كتلة catch أو خروج مبكر بـ guard:

swift
func processTransaction() throws {
    let db = try openDatabase()
    defer { closeDatabase(db) }

    let user = try fetchUser(from: db)
    defer { logAudit(user) }

    let result = try performPayment(user)
    sendNotification(result)
    // سيتم استدعاء closeDatabase(db) و logAudit(user)
    // عند أي throw أو return
}

مهم: defer يتم تنفيذه قبل نقل التحكم خارج كتلة catch، ولكن بعد حدوث الخطأ. إذا تم رمي خطأ داخل defer، لا يسمح Swift باستخدام try مباشرة داخل defer — تحتاج إلى try? أو try!. وفقًا لوثائق Apple، لا يسمح Swift للخطأ بالهروب من defer، لأن ذلك سينتهك ضمان تنفيذ الكتلة.

ضع defer مباشرة بعد الحصول على المورد. هذا يتبع مبدأ القرب: القارئ يرى الحصول والتحرير معًا، مما يحسن موثوقية الكود ويبسط مراجعة الكود.

قواعد النطاق

defer يتم تنفيذه عند الخروج من النطاق الذي تم إعلانه فيه. إذا تم إعلان defer داخل كتلة do، يتم تنفيذه عند الخروج من تلك الكتلة، وليس من الدالة الخارجية. إذا كان داخل حلقة for — في كل تكرار:

swift
func scopeExample() {
    print("start")
    do {
        defer { print("defer كتلة do") }
        print("inside do")
    }
    // "defer كتلة do" يُطبع هنا
    print("after do")
}
    // الإخراج: start, inside do, defer كتلة do, after do

for i in 1...3 {
    defer { print("end iteration \(i)") }
    print("iteration \(i)")
}
    // الإخراج: iteration 1, end iteration 1, iteration 2, end iteration 2, ...

المتغيرات التي يلتقطها defer تُقرأ في لحظة الخروج من النطاق، وليس في لحظة إعلان defer. إذا تغير متغير بين إعلان defer ونهاية النطاق، سيرى defer القيمة الأخيرة. هذا فرق مهم عن الإغلاقات (closures)، حيث يحدث الالتقاط في وقت الإنشاء. كن حذرًا: التغييرات في متغير بعد إعلان defer ستؤثر على تنفيذه.

الأخطاء الشائعة مع defer

الخطأ الأول — افتراض ترتيب تنفيذ مختلف عن LIFO. إذا كان ترتيب التنظيف مهمًا وتم إعلان defer بترتيب خاطئ، قد يتم تحرير الموارد بانتهاك التبعيات. الحل: أعلن defer مباشرة بعد الحصول على كل مورد. تم فتح المورد الثاني → defer { close second } قبل إغلاق الأول.

الخطأ الثاني — استخدام defer لمنطق لا علاقة له بالتنظيف. defer مصمم للتنظيف المضمون، وليس للتحكم الرئيسي في التدفق. إذا كان الكود داخل defer يؤثر على قيمة الإرجاع، فهذا دائمًا خطأ تقريبًا. defer لا يمكنه تغيير قيمة إرجاع الدالة (على عكس finally في Java، حيث أن return في finally يستبدل return الأصلي).

الخطأ الثالث — رمي خطأ من defer. Swift يمنع try داخل defer إذا كان الخطأ قد ينتشر للخارج. استخدم try? أو try! للعمليات التي قد ترمي خطأ، أو ضعها في دالة منفصلة بدون throws. وفقًا لـ O’Reilly “Swift in Depth” (2025)، الممارسة الجيدة هي جعل دوال التنظيف غير رامية (non-throwing) أو معالجة الأخطاء داخل defer.

الأسئلة الشائعة

ما هو defer في Swift؟

defer هي بنية في Swift تؤجل تنفيذ كتلة حتى يخرج النطاق الحالي. يتم تنفيذ الكتلة دائمًا — عند return أو throw أو break أو الإنهاء العادي. تُستخدم للتنظيف المضمون للموارد: إغلاق الملفات، تحرير الأقفال.

بأي ترتيب يتم تنفيذ عدة defer؟

بترتيب عكسي للإعلان (LIFO) — آخر defer معلن يتم تنفيذه أولاً. هذا يضمن تنظيفًا صحيحًا للموارد المتداخلة: إذا تم فتح المورد B بعد A، سيتم إغلاقه قبل A، مما يمنع الاعتماد على الموارد التي تم تحريرها بالفعل.

هل يمكن رمي خطأ من defer؟

ليس بشكل مباشر — Swift يمنع انتشار الخطأ من defer. استخدم try? أو try! للعمليات التي قد ترمي خطأ. أفضل ممارسة هي جعل دوال التنظيف غير رامية أو معالجة الأخطاء داخل defer دون نشرها للخارج.

ما الفرق بين defer و do-catch-finally؟

defer مرتبط بنطاق ويتم تنفيذه عند أي خروج، بما في ذلك return و throw و break. finally (في لغات أخرى) مرتبط بـ try-catch ويتم تنفيذه فقط عند وجود try. Swift ليس لديه finally — defer يغطي هذا السيناريو بالكامل ويعمل لأي نطاق، وليس فقط لمعالجة الأخطاء.

هل يرى defer تغييرات المتغيرات بعد إعلانه؟

نعم، defer يقرأ المتغيرات في لحظة الخروج من النطاق، وليس في لحظة الإعلان. إذا تغير متغير بعد إعلان defer، سترى كتلة defer القيمة الأخيرة. هذا يختلف عن الإغلاقات العادية، حيث يتم تثبيت الالتقاط في وقت الإنشاء.

الملخص

  • defer — كتلة إنهاء يتم تنفيذها عند الخروج من النطاق بغض النظر عن السبب (return, throw, break, الإنهاء العادي)
  • ترتيب LIFO — يتم تنفيذ عدة defer من الأسفل إلى الأعلى، آخر defer معلن يتم تنفيذه أولاً
  • تنظيف الموارد — حالة الاستخدام الرئيسية: إغلاق الملفات، تحرير الأقفال، تحرير المؤشرات، إيقاف الرسوم المتحركة
  • التوافق مع throws: يتم تنفيذ defer بعد الخطأ ولكن قبل الخروج من كتلة catch؛ أخطاء defer لا تنتشر
  • النطاق: يتم تنفيذ defer عند الخروج من النطاق الذي تم إعلانه فيه — كتلة do، حلقة، دالة
  • التقاط المتغيرات: defer يقرأ القيم في وقت الخروج، وليس وقت الإعلان — كن حذرًا مع التغييرات
  • أفضل ممارسة: أعلن defer مباشرة بعد الحصول على المورد، لا تستخدم defer لمنطق الأعمال، اجعل دوال التنظيف غير رامية

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا