به نام خدا

یکی از مباحث مهم توی هر زبانی طریقه هندلینگ ارور های موجود در برنامه به بهترین شکل هست

سویفت این کارو به بهترین شکل ممکن انجام میده

لطفا تا پایان مطلب با دقت مطالعه کنید و هرجایی که گنگ یا بود یا ابهام داشت امکان داره با مرور دوباره و یا حتی ادامه دادن به خواندن مطلب ابهامش برطرف بشه.

توی اکثرا زبان ها یه کلمه کلیدی به نام Throw وجود داره که کارش اینه که وسط تابع٬ تابع رو متوقف کنه و برگرده به جایی که تابع صدا زده شده اما اینکارو چه فرقی با break کردن تابع داره. فرقش اینه که اینکارو فقط زمانی انجام میدیم که بخوایم یه مشکل یا اروری که در حین کار توی تابع بهش برخورد کردیم برگردونیم و ادامه دادن کار تابع امکان پذیر نباشه

مثلا فرض کنید که قراره یه تابع داشته باشید که فایلی به اسم a.c رو از توی پوشه m بریزه توی پوشه n:

توی این مثال اول باید ببینید حافظه در دسترس هست یا نه اگر در دسترس نیست همینجا باید تابع متوقف بشه و یه برگرده به خطی که صدا زده شده و بهش بگه چه اتفاقی افتاده

این تازه اول کار بود و ممکن بود اگرم حافظه در دسترس باشه فایلی به نام a.c وجود نداشته باشه و یا در ادامه اصلا فولدر هایی به نام m و n نداشته باشیم

حالا فکر کنم بهتر درک کردید که قراره امروز چی یاد بگیریم

خب تابع هایی که میتونن وسط کار متوقف بشن و یه اروری رو برگردونن بهشون میگن تابعی که قابلیت دور انداختن داره یا به قولی Throwable هستن یعنی میتونن وسط کار برگردن و اروری رو برگردونن این توابع جز توابع خطرناکن چون اگه درست پیاده نشن برنامه رو سریع میبندن برای اینکه این اتفاق نیفته زبانی مثل جاوا بهتون میگه که شما باید تابعی که قابلیت throw کردن رو داره درون یه چیزی به نام try بزارید که کارش اینه که تلاش میکنه تابع رو درست تا تهش اجرا کنه و اگه اجرا کنه که همه چیز درست پیش میره ولی اگه خدایی نکرده وسط کار تابع یه اروری رو برگردونه و متوقف بشه از try در میاد و وارد catch میشه که این دو کلمه به try-catch معروفن که فکر میکنم حتما اسمشونو شنیده بودید

یه نمونه توی جاوا ببینید 

try
{
     //statements that may cause an exception
}
catch (Exception e)‏
{
     //error handling code
}

خب اگه دقت کنید جاوا اسم اون ارور ها رو گذاشته استثنا Exception اما توی سویفت بهشون میگیم Error

خب حالا میریم سراغ سینتکس سویفت توی این موضوع

اول یه تابع که قابلیت throw شدن داره رو باید بسازیم ولی خب وقتی میخوایم یه چیزی رو throw کنه باید چی رو throw کنه؟؟

پس باید اول یه مفهوم دیگه رو یاد بگیریم اونم اینه که توی سویفت برای اینکه بتونید ارور ها رو درست مدیریت کنید باید حتما اول از همه کارا یه Enum بسازید که از نوع Error باشه

Error دیگه چه نوعیه؟‌خودش یه Enum خالیه اما برای اینکه شما بتونید از اون Enum برای Throw استفاده کنید مجبورید اون Enum رو از نوع Error قرار بدید

این شکل

 enum FileErros : Error {
        case InvalidFile
        case InvalidePath
        case NotFoundPic
    }

خب حالا میتونیم یه تابع تعریف کنیم که قابل Throw باشه (البته فعلا ربطی به اون enum نداره یه مرحله پایین تر میبینم enum کجا استفاده میشه)

 func loadPic () throws {
    }

این تابع کاملا یه تابع سالمه یعنی هیچ اروری بهش نمیگیره چون داشتن throw توی تابع الزامی نیست با اینکه قابلیت throw رو داشته باشه

خب با کلمه کلیدی throws اشنا میشید که تهش یه s داره و فقط همینجا که میخواید تابع رو تعریف کنید استفاده میشه و البته توجه کنید اگر قرار باشه تابع یه مقداری رو برگردونه باید اون نوع متغییری که قراره return بشه رو بعد از این کلمه کلیدی بزارید یعنی اینشکلی

func loadPic () throws -> String {
        return "/mypic.jpg"
    }

خب حالا یه تابع ساده ای شد که تنها کارش اینه که یه مقدار همیشه ثابت رو برمیگردونه اما ما میخوایم اول چک کنیم که عکس توی گوشی کاربر هست یا نه اگر نیست که باید تابع برگرده ارور

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

func loadPic () throws -> String {
        let address : String = ...
        if (address == nil) {
            throw FileErros.NotFoundPic
        }
        return address
    }

کار تابع معلومه که قراره یه ادرسی رو پیدا کنه و ممکنه نال برگردونه و چک میکنیم اگه خالی بود برگرده و ارور بده و بگه پیدا نشد (یک تابع ممکن است چندین throw با ارور های مختلف داشته باشد)

خب وقتی تابع بالا رو توی یه تابع دیگه یا توی تابع viewDidLoad صدا بزنید میبنید که بهتون میگه باید یا تابعتون trows باشه یعنی تابعی که توش یه تابع throw رو صدا میکنید باید از اون نوع باشه که اونم بتونه پرتاب بشه یا اینکه باید براش مثل جاوا try-catch بزارید که توی سویفت یکم فرق داره و این شکلیه

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}

خب اگه بخوام یه خورده قشنگ تر بگم و با همین مثال خودمون پیادش کنم این شکلی میشه

do {
    try loadPic()
} catch FileErros.NotFoundPic {
    print("not found error")
}  catch {
    print(error)
}

دقت کنید که همیشه لازم نیست نوع ارور رو بدونید و میتونید جلوی catch رو خالی بزارید مثل بالا که اخرش. یه catch داره

این کار باعث میشه که اگه ارورتون احیانا از هیچ نوعی نبود که شما فکرشو میکردید وارد catch اخری میشه (فکر کنم اون catch اخری الزامی باشه)

خب اما گاهی پیش میاد که شما نیاز ندارید که ارور رو هندل کنید یا اینکه فقط میخواید اون تابع اجرا بشه و اگرم نشد براتون مهم نیست و به نتیجه اش زیاد کار مهمی ندارید و برنامه بدون نتیجه اش میتونه پیش بره

در اینصورت کافیه که به جای اون همه خط کد فقط با یه خط اینجوری بنویسید

let x = try? someThrowingFunction()

این کار باعث میشه که سعی خودشو بکنه اما اگه نتونست مقدار nil رو بریزه توی x که اینجوری نوشتن گاهی خیلی سریع و کمک کننده است

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

if let x = try? someThrowingFunction() {
    //do anything
}

اینجوری نوشتن یعنی اگه مقداری که داری توی ایکس میریزی null نبود بدنه ایف رو اجرا کن

مشکلی که این روش داره اینه که شما دسترسی به اون error برگشتی ندارید یعنی اگه error برگشته بشه شما نمیتونید تصمیم بفهمید ارور از کجاست که یه کار درستی رو مطابق با اون انجام بدید (اینکار رو که از try? استفاده کنیم فقط زمانی به درد میخوره که نتیجه آنچنان مهم نیست و بدون فهمیدن ارور هم کارمون راه میفته)

گاهی هم مطمئن هستیم که تابعی که از نوع throw هست قطعا بدون ارور اجرا میشه یا اصلا انقدر مطمئنیم که میخوایم بگیم اگه اجرا نشد برنامه اصلا بسته بشه (چون گفتم این توابع خطرناکن.و برنامه رو میبندن اگر درست نوشته نشن)

وقتی که این همه اطمینان داریم میتونیم اینجوری بنویسیم

try! throwableFunction()

که در این مواقع اگه خدایی نکرده تابع یه throw برگردونه برنامه عزیز ما کاملا بسته میشه

 

حالا بریم سراغ استفاده از defer توی توابع throws:

اصلا اول بیایم بگیم defer چیه و چه کاربردی داره؟ خب ببینید گاهی شما میخواید اول تابع که دارید مینویسید که چیزایی رو بنویسید که آخر تابع اجرا بشن ولی همون بالا باشن چون ممکنه یادتون بره بعدا پایین بنویسیدشون یا اینکه دلیلی دارید به هر حال میخواید اونا رو بالای تابع بنویسید ولی اخر اخر تابع اجرا بشن 

اینکارو defer براتون میکنه به این شکل

func mySampleFunction (){
    print("befor")
    defer {
        print("last of func")
    }
    print("after")
}

وقتی کد بالا رو اجرا میکنید خروجی زیر رو به شما میده

before
after
last of func

جالبه نه؟

خب حالا یه مثالی ازش میزنم مثلا شما میخواید وقتی یه فایلی رو باز کردید اونو آخر تابع ببندید یا اگه دارید از سنسوری از گوشی استفاده میکنید آخر تابع سنسور رو آزاد کنید پس همون جا که فایل رو open یا سنسور رو در دسترس میگیرید زیر یه defer میزارید و فایل رو میبندید یا اینکه سنسور رو ازاد میکید

حالا ممکنه یه مواقعی که دارید با فایل کار میکنید یه اتفاقی بیفته که تابع قراره با یه ارور throw بشه

تکلیف فایل باز چی میشه و آیا defer این بار هم اجرا میشه؟

باید به عرضتون برسونم اگر defer رو بالای throw نوشته باشید اجرا میشه یعنی در اخرین لحظه که تابع میخواد برگرده اون defer رو اجرا میکنه.و برنامه بسته میشه یا اینکه برمیگرده سر جایی که تابع رو صدا زده بودید اما اگر پایین throw بنویسید دیگه اجرا نمیشه

حالا بهتون میگم چرا اینجوری کردن که بالا و پایین مهم شده

چون ممکنه شما اول یه چیزی رو بررسی کنید و بعد بخواید. throw کنید قبل از اینکه اصلا فایل رو باز کرده باشید و باز کردن فایل رو بعد از اون بررسی نوشته باشید که در اینصورت نباید defer رو بالای تابع تعریف کنید بلکه باید جایی تعریف کنید که فایل رو دارید باز میکنید که هر موقع ارور مربوط قرار شد ارور مربوط به فایل برگردونده بشه اول defer اجرا بشه و بعد کارهای دیگه اتفاق بیفته خداییش خیلی چیز جالبیه این سویفت اینطور نیست؟

امیدوارم موفق باشید

به امید ایرانی سربلند و سرافراز

تا اموزش های بعدی یاعلی مدد