defer — یک ساختار کنترلی در Swift است که اجرای بلوک کد را به لحظه خروج از محدوده دید جاری (scope) برنامهریزی میکند. بلوک defer صرفنظر از روش پایانیافتن اجرا میشود — return، break، throw، fatalError یا پایان عادی. طبق Swift Language Guide (2025)، هنگام وجود چندین defer در یک scope، آنها به ترتیب معکوس اعلام اجرا میشوند — آخرین defer اعلامشده اولین بار اجرا میشود (LIFO). این امر defer را برای پاکسازی تضمینی منابع ضروری میکند: بستن توصیفگرهای فایل، برداشتن قفلها، آزادسازی اشارهگرهای موقت بدون خطر جا انداختن cleanup در خروج زودهنگام.
اصلی
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 در یک scope اعلام میشوند، به ترتیب معکوس اعلام اجرا میشوند (LIFO — Last In, First Out). یعنی آخرین defer اعلامشده اول اجرا میشود و اولین defer آخر:
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 — پاکسازی تضمینی منابع. کار با سیستم فایل را در نظر بگیرید. باز کردن فایل از طریق FileHandle نیاز به بستن صریح دارد — defer تضمین میکند که close در هر سناریویی فراخوانی شود:
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 به طور مؤثر با توابع throws ترکیب میشود. وقتی تابع میتواند در هر مرحله خطا پرتاب کند، defer بدون تکرار کد در هر بلوک catch یا خروج زودهنگام guard، cleanup را تضمین میکند:
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 باشد — در هر تکرار:
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 روی اجرای آن تأثیر میگذارد.
اولین اشتباه — فرض ترتیب اجرای متفاوت از 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 که اجرای بلوک را تا خروج از محدوده دید جاری به تأخیر میاندازد. بلوک همیشه اجرا میشود — در return، throw، break یا پایان عادی. برای پاکسازی تضمینی منابع استفاده میشود: بستن فایلها، برداشتن قفلها.
به ترتیب معکوس اعلام (LIFO) — آخرین defer اعلامشده اول اجرا میشود. این cleanup صحیح منابع تو در تو را تضمین میکند: اگر منبع B بعد از A باز شده باشد، قبل از A بسته میشود که از وابستگی به منابع قبلاً آزادشده جلوگیری میکند.
نه مستقیماً — Swift انتشار خطا از defer را ممنوع میکند. برای عملیاتی که ممکن است خطا پرتاب کنند از try? یا try! استفاده کنید. بهترین روش این است که توابع cleanup را non-throwing کنید یا خطاها را در داخل defer بدون انتشار به بیرون مدیریت کنید.
defer به scope متصل است و در هر خروجی از جمله return، throw و break اجرا میشود. finally (در زبانهای دیگر) به try-catch متصل است و فقط در حضور try اجرا میشود. در Swift finally وجود ندارد — defer این سناریو را کاملاً پوشش میدهد و برای هر scopeای کار میکند، نه فقط برای مدیریت خطا.
بله، defer متغیرها را در لحظه خروج از scope میخواند، نه در لحظه اعلام. اگر متغیر پس از اعلام defer تغییر کند، بلوک defer آخرین مقدار را خواهد دید. این با بستهشدنهای معمولی متفاوت است که در آن گرفتن در زمان ایجاد ثابت میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید