defer — چیست، سینتکس و ترتیب اجرا

نویسنده: IT Sectr منتشر شده: 2026-06-20 زمان مطالعه: 8 دقیقه

defer — یک ساختار کنترلی در Swift است که اجرای بلوک کد را به لحظه خروج از محدوده دید جاری (scope) برنامه‌ریزی می‌کند. بلوک defer صرف‌نظر از روش پایان‌یافتن اجرا می‌شود — return، break، throw، fatalError یا پایان عادی. طبق Swift Language Guide (2025)، هنگام وجود چندین defer در یک scope، آنها به ترتیب معکوس اعلام اجرا می‌شوند — آخرین defer اعلام‌شده اولین بار اجرا می‌شود (LIFO). این امر defer را برای پاکسازی تضمینی منابع ضروری می‌کند: بستن توصیف‌گرهای فایل، برداشتن قفل‌ها، آزادسازی اشاره‌گرهای موقت بدون خطر جا انداختن cleanup در خروج زودهنگام.

اصلی

  • defer — بلوک کدی که هنگام خروج از محدوده دید، صرف‌نظر از دلیل خروج اجرا می‌شود (return, throw, break)
  • ترتیب LIFO: چندین defer از پایین به بالا اجرا می‌شوند — آخرین اعلام‌شده اول اجرا می‌شود
  • پاکسازی منابع — کاربرد اصلی: بستن فایل‌ها، برداشتن قفل‌ها، پایان انیمیشن‌ها
  • تعریف متغیرها: defer متغیرها را در لحظه خروج می‌بیند، نه در لحظه اعلام
  • جایگزین نمی‌کند do-catch را برای مدیریت خطا — defer مسئول cleanup است نه کنترل جریان

defer چیست؟

defer — یک ساختار کنترلی زبان Swift است که در Swift 2.0 (2015) معرفی شد و اجرای بلوک خود را تا لحظه پایان scope جاری به تأخیر می‌اندازد. ویژگی کلیدی: defer اجرای بدنه خود را صرف‌نظر از نحوه دقیق پایان scope تضمین می‌کند — موفق (return)، با خطا (throw)، زودهنگام (break, continue) یا مرگبار (fatalError, precondition).

از نظر نحوی defer به صورت defer { /* کد */ } نوشته می‌شود و می‌تواند در هر جایی از scope قرار گیرد. کامپایلر Swift تضمین می‌کند که کد داخل defer اجرا شود، حتی اگر بین اعلام defer و انتهای scope یک استثنا یا return رخ دهد. این اساساً defer را از کد معمولی که در انتهای تابع قرار می‌گیرد و ممکن است در خروج زودهنگام نادیده گرفته شود متمایز می‌کند.

طبق مقاله کریس لتنر (سازنده Swift، 2015)، defer از ساختارهای مشابه در زبان‌های دیگر الهام گرفته است — defer در Go، finally در Java/Python، scope guard در C++ — اما با یک تفاوت مهم: در Swift defer در انتهای scope اجرا می‌شود، نه بلافاصله بعد از بلوک try-catch. این رفتار قابل‌پیش‌بینی‌تری برای cleanup در توابع با نقاط خروج متعدد فراهم می‌کند.

از defer برای مدیریت متقارن منابع استفاده کنید: باز کردن فایل ← defer { close }، تنظیم قفل ← defer { unlock }. چنین الگویی تضمین می‌کند که آزادسازی منبع در هیچ شرایطی جا نیفتد.

ترتیب اجرای چندین defer

وقتی چندین defer در یک scope اعلام می‌شوند، به ترتیب معکوس اعلام اجرا می‌شوند (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 تضمین می‌کند که قفل‌ها به ترتیب معکوس گرفتن آزاد می‌شوند و از بن‌بست جلوگیری می‌کند.

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 فراخوانی می‌شود
}

سناریوی رایج دیگر — انیمیشن‌های UI با پرچم بارگذاری. قبل از شروع بارگذاری پرچم isLoading = true تنظیم می‌شود و defer آن را هنگام خروج از تابع، صرف‌نظر از موفقیت یا خطای درخواست، به false تغییر می‌دهد. این از وضعیتی جلوگیری می‌کند که پرچم به دلیل خطای پردازش‌نشده true باقی بماند و رابط را برای همیشه مسدود کند.

طبق Bitbucket Engineering Blog (2024)، defer همچنین برای پروفایل‌گیری استفاده می‌شود: در ابتدای تابع می‌توان زمان را ثبت کرد و در defer — تفاوت را محاسبه و چاپ کرد. این اندازه‌گیری دقیق عملکرد همه مسیرهای اجرا، از جمله مسیرهای خطا، را فراهم می‌کند.

Defer و مدیریت خطا

defer به طور مؤثر با توابع throws ترکیب می‌شود. وقتی تابع می‌تواند در هر مرحله خطا پرتاب کند، defer بدون تکرار کد در هر بلوک catch یا خروج زودهنگام guard، cleanup را تضمین می‌کند:

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 فراخوانی می‌شوند
    // در هر throw یا return
}

مهم: defer قبل از انتقال کنترل از بلوک catch، اما بعد از وقوع خطا اجرا می‌شود. اگر در defer خطایی پرتاب شود، Swift اجازه استفاده مستقیم از try در داخل defer را نمی‌دهد — try? یا try! لازم است. طبق Apple Documentation، Swift اجازه نمی‌دهد خطا از defer «بیرون بزند» زیرا این امر تضمین اجرای بلوک را نقض می‌کند.

defer را بلافاصله پس از گرفتن منبع قرار دهید. این از اصل نزدیکی پیروی می‌کند: خواننده گرفتن و آزادسازی را در کنار هم می‌بیند که قابلیت اطمینان کد را افزایش و بازبینی کد را ساده‌تر می‌کند.

قوانین محدوده دید

defer هنگام خروج از scopeای که در آن اعلام شده اجرا می‌شود. اگر 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)")
}
    // خروجی: تکرار 1, پایان تکرار 1, تکرار 2, پایان تکرار 2, ...

متغیرهایی که توسط defer گرفته شده‌اند در لحظه خروج از scope خوانده می‌شوند، نه در لحظه اعلام defer. اگر متغیر بین اعلام defer و انتهای scope تغییر کند، defer آخرین مقدار را خواهد دید. این تفاوت مهمی با بسته‌شدن‌ها (closures) است که در آن گرفتن در لحظه ایجاد انجام می‌شود. مراقب باشید: تغییرات متغیر پس از اعلام defer روی اجرای آن تأثیر می‌گذارد.

اشتباهات رایج با defer

اولین اشتباه — فرض ترتیب اجرای متفاوت از LIFO. اگر ترتیب cleanup مهم باشد و deferها به ترتیب نادرست اعلام شده باشند، ممکن است منابع با نقض وابستگی آزاد شوند. راه‌حل: بلافاصله پس از گرفتن هر منبع defer را اعلام کنید. منبع دوم باز شد ← defer { close second } قبل از بسته شدن منبع اول.

دومین اشتباه — استفاده از defer برای منطق نامرتبط با پاکسازی. defer برای cleanup تضمینی طراحی شده است نه برای جریان اصلی کنترل. اگر کد در defer روی مقدار بازگشتی تأثیر بگذارد، تقریباً همیشه اشتباه است. defer نمی‌تواند مقدار return تابع را تغییر دهد (برخلاف Java finally که در آن return در finally return اصلی را بازنویسی می‌کند).

سومین اشتباه — پرتاب خطا از defer. Swift try را در داخل defer ممنوع می‌کند اگر خطا بتواند به بیرون منتشر شود. برای عملیاتی که ممکن است خطا پرتاب کنند از try? یا try! استفاده کنید یا آنها را در یک تابع جداگانه بدون throws بپیچید. طبق O'Reilly «Swift in Depth» (2025)، روش خوب این است که توابع cleanup را غیر پرتاب‌کننده (non-throwing) کنید یا خطاها را در داخل defer مدیریت کنید.

سؤالات متداول

defer در Swift چیست؟

defer — ساختار کنترلی Swift که اجرای بلوک را تا خروج از محدوده دید جاری به تأخیر می‌اندازد. بلوک همیشه اجرا می‌شود — در return، throw، break یا پایان عادی. برای پاکسازی تضمینی منابع استفاده می‌شود: بستن فایل‌ها، برداشتن قفل‌ها.

چندین defer به چه ترتیبی اجرا می‌شوند؟

به ترتیب معکوس اعلام (LIFO) — آخرین defer اعلام‌شده اول اجرا می‌شود. این cleanup صحیح منابع تو در تو را تضمین می‌کند: اگر منبع B بعد از A باز شده باشد، قبل از A بسته می‌شود که از وابستگی به منابع قبلاً آزادشده جلوگیری می‌کند.

آیا می‌توان از defer خطا پرتاب کرد؟

نه مستقیماً — Swift انتشار خطا از defer را ممنوع می‌کند. برای عملیاتی که ممکن است خطا پرتاب کنند از try? یا try! استفاده کنید. بهترین روش این است که توابع cleanup را non-throwing کنید یا خطاها را در داخل defer بدون انتشار به بیرون مدیریت کنید.

تفاوت بین defer و do-catch-finally چیست؟

defer به scope متصل است و در هر خروجی از جمله return، throw و break اجرا می‌شود. finally (در زبان‌های دیگر) به try-catch متصل است و فقط در حضور try اجرا می‌شود. در Swift finally وجود ندارد — defer این سناریو را کاملاً پوشش می‌دهد و برای هر scopeای کار می‌کند، نه فقط برای مدیریت خطا.

آیا defer تغییرات متغیرها را پس از اعلام خود می‌بیند؟

بله، defer متغیرها را در لحظه خروج از scope می‌خواند، نه در لحظه اعلام. اگر متغیر پس از اعلام defer تغییر کند، بلوک defer آخرین مقدار را خواهد دید. این با بسته‌شدن‌های معمولی متفاوت است که در آن گرفتن در زمان ایجاد ثابت می‌شود.

خلاصه

  • defer — بلوک نهایی‌سازی که هنگام خروج از scope صرف‌نظر از دلیل اجرا می‌شود (return, throw, break, پایان عادی)
  • ترتیب LIFO — چندین defer از پایین به بالا اجرا می‌شوند، آخرین اعلام‌شده — اولین اجراشده
  • پاکسازی منابع — کاربرد اصلی: بستن فایل‌ها، برداشتن قفل‌ها، آزادسازی اشاره‌گرها، توقف انیمیشن‌ها
  • سازگاری با throws: defer بعد از خطا اما قبل از خروج از بلوک catch اجرا می‌شود؛ خطا از defer منتشر نمی‌شود
  • Scope: defer هنگام خروج از scopeای که در آن اعلام شده اجرا می‌شود — بلوک do، حلقه، تابع
  • گرفتن متغیرها: defer مقادیر را در لحظه خروج می‌خواند نه در لحظه اعلام — مراقب تغییرات باشید
  • بهترین روش: defer را بلافاصله پس از گرفتن منبع اعلام کنید، از defer برای منطق تجاری استفاده نکنید، توابع cleanup را non-throwing کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید