Throw و Throws — مکانیزمهای پرتاب و اعلام استثنا در زبانهای برنامهنویسی. عملگر throw اجرای عادی تابع را متوقف کرده و شیء خطا را به بالای پشته فراخوانی منتقل میکند. کلمه کلیدی throws در امضای تابع، طرف فراخواننده را از احتمال خطا آگاه میکند و کد را قابل پیشبینی میسازد. بر اساس Kotlin Documentation (2026)، throw در Kotlin یک عبارت (expression) است نه دستور (instruction)، که امکان استفاده از آن را در داخل بلوکهای when و عملگر elvis فراهم میکند.
نکات اصلی
Throw — عملگری است که در نقطه اجرای برنامه استثنا تولید میکند. هنگامی که throw مواجه میشود، جریان اجرای فعلی بلافاصله متوقف شده و کنترل به نزدیکترین کنترلکننده catch در پشته فراخوانی منتقل میشود. اگر کنترلکنندهای یافت نشود، برنامه به صورت غیرعادی خاتمه مییابد. Throw در توسعه موبایل برای علامتدهی خطاهایی استفاده میشود که در سطح فعلی انتزاع قابل پردازش نیستند — برای مثال، پاسخ نادرست سرور، عدم وجود شبکه یا آرگومانهای نامعتبر.
Throws — اصلاحکننده در امضای تابع (عمدتاً در Swift) که اعلام میکند تابع میتواند خطا پرتاب کند. این بخشی از مکانیزم checked errors در Swift است: طرف فراخواننده موظف است خطا را از طریق do-catch، try?، try! پردازش کند یا تابع خود را با throws برای انتقال بیشتر (propagation) علامتگذاری کند. در Kotlin و Java throws نیز وجود دارد، اما Kotlin آن را اضافی میداند — همه استثناها در Kotlin unchecked هستند، یعنی میتوانند بدون اجبار نحوی پردازش نشوند. بر اساس Apple Swift Documentation (2026)، throws در Swift — تنها راه اعلام صریح احتمال خطا در نوع تابع است که قراردادهای API را برای توسعهدهنده شفاف میسازد.
تفاوت بین throw و throws بنیادی است: throw — یک عمل است (پرتاب استثنا در زمان اجرا)، throws — یک اعلام است (قرارداد زمان کامپایل). تابع بدون throws نمیتواند از throw استفاده کند — کامپایلر Swift خطا خواهد داد. تابع با throws ممکن است از throw استفاده نکند — این مجاز است اما بیمعنی. این تفکیک throw/throws را به ابزاری قدرتمند در طراحی API تبدیل میکند، جایی که قرارداد خطا در امضای تابع قبل از فراخوانی آن قابل مشاهده است.
در Swift عملگر throw هر نوعی را که پروتکل Error را پیادهسازی میکند میپذیرد. اغلب این یک enum با caseهایی برای انواع مختلف خطاها است. Swift از checked exceptions به سبک Java پشتیبانی نمیکند — در عوض نوع تابع با throws علامتگذاری شده و پردازش بر عهده طرف فراخواننده است. این باعث میشود throw در Swift انعطافپذیرتر اما همچنین برای توسعهدهنده مسئولیتپذیرتر باشد.
هر نوعی که با پروتکل Error مطابقت داشته باشد میتواند از طریق throw پرتاب شود. اغلب توسعهدهندگان از enum با caseهای بدون مقدار مرتبط (برای خطاهای ساده) یا با associated values (برای انتقال زمینه) استفاده میکنند. Swift نیازی به enum بودن خطا ندارد — میتوان از struct یا class پیادهسازیکننده Error استفاده کرد، اما enum به دلیل exhaustive switch در سمت کنترلکننده ترجیح داده میشود. کامپایلر بررسی میکند که همه caseها در do-catch پردازش شدهاند.
enum AuthError: Error {
case invalidCredentials
case tokenExpired
case accountLocked(remainingMinutes: Int)
}
func login(username: String, password: String) throws -> Session {
guard isValid(username) else {
throw AuthError.invalidCredentials
}
let response = try api.authenticate(username, password)
if response.isLocked {
throw AuthError.accountLocked(
remainingMinutes: response.lockDuration
)
}
return Session(token: response.token)
}
AuthError سه سناریو را تعریف میکند: اعتبارنامه نامعتبر، توکن منقضی شده و حساب مسدود شده با مقدار مرتبط remainingMinutes. تابع login به عنوان throws اعلام شده است — کامپایلر نیاز دارد که از طریق try فراخوانی شود. در داخل تابع throw در دو مکان استفاده میشود: هنگام username نامعتبر و هنگام حساب مسدود شده. مقدار مرتبط accountLocked به کاربر امکان میدهد اطلاعات مشخصی دریافت کند — چند دقیقه تا باز شدن قفل صبر کند. این رویکرد نیاز به endpoints API جداگانه برای بررسی وضعیت قفل را از بین میبرد.
در Kotlin throw — یک عبارت (expression) از نوع Nothing است، نه یک دستور. این بدان معناست که throw میتواند در سمت راست انتساب، داخل عبارات when و عملگر elvis ?: استفاده شود. نوع Nothing — زیرنوع ویژه همه انواع در Kotlin است که امکان استفاده از throw را در مکانهایی که مقدار از هر نوعی نیاز است فراهم میکند. کامپایلر میفهمد که پس از throw اجرا ادامه نمییابد و برای این مورد شاخهای نیاز ندارد.
Nothing — نوع منحصر به فرد در Kotlin که زیرنوع همه انواع ممکن است. تابعی که Nothing برمیگرداند (برای مثال TODO()) هرگز به صورت عادی پایان نمییابد — یا همیشه استثنا پرتاب میکند یا وارد حلقه بینهایت میشود. این throw را به گزینه طبیعی برای استفاده در مکانهایی که نیاز به مقدار است تبدیل میکند: عملگر elvis، when بدون else، مقداردهی متغیر. اگر throw در شاخه when قرار گیرد، کامپایلر میفهمد که شاخه به Nothing منتهی میشود و برای این شاخه return یا else نیاز ندارد.
data class Config(val apiUrl: String, val timeoutSec: Int)
class ConfigParser {
fun parse(json: String): Config {
val obj = JSONObject(json)
val url = obj.optString("apiUrl")
?: throw IllegalArgumentException("apiUrl is required")
val timeout = obj.optInt("timeoutSec", 30)
return Config(url, timeout)
}
fun getErrorMessage(code: Int): String {
return when (code) {
404 -> "Not found"
500 -> "Server error"
else -> throw IllegalArgumentException("Unknown code: $code")
}
}
}
در مثال اول throw در عملگر elvis ?: استفاده شده است: اگر فیلد apiUrl در JSON وجود نداشته باشد، عبارت throw بلافاصله اجرا را متوقف کرده و IllegalArgumentException پرتاب میکند. نوع Nothing به کامپایلر اجازه میدهد نوع سمت راست را به عنوان String استنباط کند (elvis انتظار String دارد، throw دارای نوع Nothing است، Nothing زیرنوع String است). در مثال دوم throw داخل عبارت when: اگر کد با هیچکدام از موارد شناخته شده مطابقت نداشته باشد، استثنا پرتاب میشود. کامپایلر میفهمد که پس از throw کد غیرقابل دسترس است، بنابراین نوع بازگشتی تابع String نقض نمیشود.
در Swift throws بعد از لیست پارامترها و قبل از پیکان نوع بازگشتی مشخص میشود. تابع با throws میتواند فقط توابع throws دیگر را در داخل do-catch یا با try? فراخوانی کند. اگر تابع throws خطا را پردازش نکند، آن را به طرف فراخواننده منتقل میکند. Swift همچنین rethrows را پشتیبانی میکند — اصلاحکنندهای برای توابع مرتبه بالاتر که یک closure با throws دریافت کرده و خطای آن را منتقل میکند. Rethrows به این معنی است که تابع فقط در صورتی خطا پرتاب میکند که closure ارسالی آن را پرتاب کرده باشد — خود تابع خطا ایجاد نمیکند.
func mapValues<T>(
_ array: [T],
transform: (T) throws -> U
) rethrows -> [U] {
var result = [U]()
for element in array {
result.append(try transform(element))
}
return result
}
// استفاده با throws-بسته
let parsed = try mapValues(jsonStrings) { str in
let data = Data(str.utf8)
return try JSONDecoder().decode(Item.self, from: data)
}
Rethrows به تابع mapValues اجازه میدهد انعطافپذیر باشد: هم closures throws و هم معمولی را میپذیرد. اگر closure throws ارسال شود، فراخوانی mapValues نیاز به try دارد؛ اگر معمولی باشد — try لازم نیست. این rethrows را برای توابع مرتبه بالاتر مانند map، filter، reduce در کتابخانه استاندارد Swift ایدهآل میکند. توصیه Apple: از rethrows برای APIهایی که closures throws میپذیرند و تنها منبع خطا همان closure است استفاده کنید. اگر تابع میتواند خطای خود را پرتاب کند، از throws استفاده کنید.
Swift throws به checked exceptions (مانند Java) نزدیکتر است — خطاهای پرتاب شده در امضا اعلام میشوند. Kotlin و Dart از استثناهای unchecked استفاده میکنند — throws در امضا الزامی نیست. تفاوت اساسی است: checked توسعهدهنده را مجبور به پردازش میکند (ایمنتر اما پرگفتارتر)، unchecked آزادی میدهد اما خطر فراموش کردن پردازش خطا را افزایش میدهد. Swift برای throws checked را انتخاب کرده، Kotlin — unchecked را برای همه استثناها. هر دو رویکرد مزایایی دارند: Swift در سطح زبان قابل اعتمادتر است، Kotlin فشردهتر و در زنجیرههای تبدیل تابعی راحتتر است.
برای پردازش ساختاریافته خطاها در برنامههای موبایل توصیه میشود به جای استفاده از Exception یا Error پایه، انواع خطای خود را ایجاد کنید. در Swift برای این کار از enum با پروتکل Error، در Kotlin — sealed class با ارثبری از Throwable (یا از Exception)، در Dart — class با ارثبری از Exception استفاده میشود. انواع سفارشی امکان گروهبندی خطاها بر اساس دستهبندی و انتقال دادههای مرتبط را فراهم میکنند.
| زبان | نوع خطا | ویژگی |
|---|---|---|
| Swift | enum: Error { ... } | مقادیر مرتبط، exhaustive switch در catch |
| Kotlin | sealed class : Throwable() | Data class برای خطاها با فیلدها، عبارت when |
| Dart | class implements Exception | فیلد Message، on-کلاز در catch |
| Java | class extends Exception | Checked vs unchecked، throws اجباری در امضا |
هنگام طراحی خطاهای سفارشی از قانون پیروی کنید: یک خطا — یک سناریو. علل مختلف را در یک نوع با پرچم String message ترکیب نکنید — برای هر سناریو case/زیرکلاس جداگانه ایجاد کنید. این به طرف فراخواننده امکان میدهد هر مورد را از طریق pattern matching (when/switch) پردازش کند نه از طریق مقایسه رشتهای. در Swift این بررسی جامع (exhaustive checking) را فراهم میکند — کامپایلر هشدار میدهد اگر caseای از enum NetworkError پردازش نشده باشد.
sealed class NetworkError(val message: String) : Throwable(message) {
data class Timeout(val durationMs: Long) :
NetworkError("Request timed out after ${durationMs}ms")
data class HttpError(val code: Int, val body: String?) :
NetworkError("HTTP $code")
data class NoConnection(val cause: IOException) :
NetworkError("No internet connection")
}
Sealed class NetworkError از Throwable (نوع استاندارد استثناها در Kotlin) ارثبری میکند. هر زیرکلاس یک data class با فیلدهای خود است: Timeout شامل مدت زمان تایماوت به میلیثانیه، HttpError — کد و بدنه پاسخ، NoConnection — IOException اصلی. چنین طراحی امکان پردازش هر خطا را از طریق when با مرور جامع فراهم میکند (وقتی زیرکلاس جدیدی اضافه میکنید، کامپایلر شما را مجبور به بهروزرسانی تمام عبارات when میکند).
Swift سه گزینه برای فراخوانی توابع throws ارائه میدهد که هر کدام قرارداد ایمنی خود را دارند. try — روش استاندارد: نیاز به do-catch یا قرار گرفتن در داخل تابع throws دارد. try? خطا را به nil تبدیل میکند — نتیجه اختیاری میشود، در صورت خطا nil بازگردانده میشود، نوع از T به T? تغییر میکند. try! — اجرای اجباری بدون پردازش: اگر خطا پرتاب شود، برنامه کرش میکند. try! را فقط زمانی استفاده کنید که کاملاً مطمئن هستید خطا غیرممکن است (مثلاً دادههای آشکارا صحیح).
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!
// try? — نتیجه اختیاری
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())
// try! — موفقیت تضمینی (فقط وقتی مطمئن هستید)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
Config.self,
from: Config.defaultJSON
)
// try — پردازش استاندارد
do {
let user = try fetchUser()
showUser(user)
} catch let error as NetworkError {
showRetryAlert(error.message)
}
در مثال try! برای JSON آشکارا معتبر که در bundle برنامه تعبیه شده استفاده میشود — خطای رمزگشایی در انتشار صحیح غیرممکن است. try? برای خواندن فایل پیکربندی استفاده میشود — اگر فایل وجود نداشته باشد یا خراب باشد، برنامه به جای کرش از مقادیر پیشفرض استفاده میکند. try در do-catch برای درخواستهای شبکه استفاده میشود، جایی که خطا مورد انتظار است و نیاز به واکنش کاربر دارد. توصیه: از try! در کد تولیدی خودداری کنید — فقط برای دادههای ثابت تأیید شده در مرحله ساخت استثنا قائل شوید.
سؤالات متداول
Throw — عملگری است که خطا را در حین اجرای برنامه پرتاب کرده و جریان را متوقف میکند. Throws — اصلاحکننده امضای تابع که اعلام میکند تابع میتواند خطا پرتاب کند. تابع بدون throws نمیتواند از throw استفاده کند. Throws یک قرارداد زمان کامپایل است، throw یک اقدام زمان اجرا.
Kotlin از فلسفه unchecked exceptions پیروی میکند: همه استثناها میتوانند بدون اجبار نحوی پردازش نشوند. توسعهدهندگان Kotlin معتقدند throws در Java منجر به بلوکهای try-catch اضافی و نادیده گرفتن checked exceptions از طریق catch خالی میشود. نوع Nothing در Kotlin امکان استفاده از throw را به عنوان عبارت فراهم میکند و throws را با روشی انعطافپذیرتر جایگزین میکند.
try! فقط زمانی مجاز است که کاملاً مطمئن باشید خطا غیرممکن است: JSON آشکارا معتبر از bundle، دادههای ثابت، طرحهای URL صحیح. در کد تولیدی try! یک استثنا است نه یک قانون. try? برای سناریوهای اختیاری با مقدار پیشفرض ترجیح داده میشود، try با do-catch برای پردازش اجباری خطا.
Rethrows — اصلاحکننده برای توابعی که closures throws میپذیرند. تابع با rethrows فقط در صورتی خطا پرتاب میکند که closure ارسالی آن را پرتاب کرده باشد. این به توابع مرتبه بالاتر (map، filter) اجازه میدهد هم با closures throws و هم non-throws بدون try اجباری در طرف فراخواننده کار کنند.
بله، در داخل catch میتوان از throw برای انتقال خطا به بالای پشته استفاده کرد، آن را در نوع دیگری پیچیده یا زمینه اضافه کرد. این error chaining یا rethrow نامیده میشود. در Swift یک throw دیگر در داخل catch کافی است، در Kotlin — throw در داخل بلوک catch. بلوک finally قبل از انتقال کنترل به بیرون اجرا میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید