آموزش برنامه نویسی سوئیفت: تابع، دامنه (Scope) و Enum — بخش پنجم

۸۸ بازدید
آخرین به‌روزرسانی: ۲۷ شهریور ۱۴۰۲
زمان مطالعه: ۱۷ دقیقه
آموزش برنامه نویسی سوئیفت: تابع، دامنه (Scope) و Enum — بخش پنجم

در بخش قبلی این سری مطالب آموزش برنامه نویسی سوئیفت به بررسی گزاره‌های if، حلقه‌های while و همچنین حلقه‌های for-in پرداختیم. برخی از این گزاره‌های if کاملاً طولانی بودند و در صورتی که بخواهیم کارهای مختلف در یک گزاره if انجام دهیم، واقعاً حجم بالایی پیدا می‌کنند و خواندشان دشوار می‌شود. در این نوشته به بررسی این موضوع و راه‌حل‌های آن خواهیم پرداخت.

تابع‌ها

در عمل می‌توان برنامه‌هایی در سوئیفت نوشت که از تابع استفاده نکنند. شاید افراد زیادی که با زبان‌های دیگر آشنا هستند فکر کنند که این یک وضعیت بدیهی است. اما واقعیت این است که چنین نیست. در ادامه این راهنما در این خصوص بیشتر صحبت خواهیم کرد؛ اما هم اینک باید بگوییم که متد نوشته شده در دامنه سراسری به عنوان نقطه ورودی برای برنامه عمل می‌کند. نقطه ورود (entry point) صرفاً یک روش برای اعلام این است که برنامه از کجا آغاز می‌شود.

ما به این دلیل تابع‌ها را می‌نویسیم که می‌خواهیم کد خود را به اجزای کوچک‌تر تقسیم کنیم تا خوانایی‌شان بهبود یابد. یک فهرست از راهنمایی‌های برنامه‌نویسی وجود دارند که باعث می‌شوند کار شما آسان‌تر شود. گرچه این فهرست جامع نیست؛ اما برای آغاز تفکر در مورد روش‌های بهبود برنامه‌نویسی مناسب به نظر می‌آید:

  • جداسازی دغدغه‌ها: اگر قرار باشد تعداد حروف یک رشته را بشمارید و سپس دو واحد به آن اضافه کنید معقول است که این کار را در دو تابع مجزا انجام دهید که یکی برای شمارش حروف در رشته‌ها استفاده می‌شود و دیگری برای افزودن عدد، مورد استفاده قرار می‌گیرد.
  • اصل مسئولیت منفرد: این اصل را می‌توان به این صورت خلاصه کرد که هر شیئی برای این که کار خود را به خوبی انجام دهد، باید وظیفه مشخصی داشته باشد.
  • تکرار نکنید (DRY): این اصل کاملاً بدیهی است، چرا باید یک متن را چندین بار تایپ کنیم؟ این کار موجب می‌شود که خوانایی برنامه کاهش یابد و توسعه‌دهندگان دیگر وقتی کد را می‌خوانند دچار سردرگمی شوند، اما جالب این جاست که همان توسعه‌دهندگان وقتی خودشان کد می‌زنند باز همین اشتباه را مرتکب می‌شوند.

در ادامه در مورد اصول فوق بیشتر توضیح خواهیم داد. اینک کار خود را با برسی مثالی از تابع نوشته شده در سوئیفت آغاز می‌کنیم. ساختار این تابع ممکن است عجیب به نظر برسد؛ اما همه موارد را توضیح خواهیم داد.

1// Define the function
2func addTwoNumbers(number firstNumber: Int,otherNumber secondNumber: Int) -> Int {
3    let functionResult = firstNumber + secondNumber
4    return functionResult
5}
6
7// Call the function
8var result = addTwoNumbers(number: 5, otherNumber: 3)
9// result is equal to 8
10result = addTwoNumbers(number: 10, otherNumber: result)
11// result is now equal to 18

کد فوق ممکن است در ابتدا موجب سردرگمی شما بشود؛ اما جای نگرانی نیست چون خط به خط آن را توضیح می‌دهیم.

ابتدا یک تابع با استفاده از کلیدواژه Func تعریف شده است. بدین ترتیب به کامپایلر اعلام می‌کنیم که ما می‌خواهیم این بلوک کد قابلیت استفاده مجدد نیز داشته باشد. سپس نام تابع را به صورت addTwoNumbers اعلان کرده‌ایم.

تابع‌ها پس از نام خود یک جفت پرانتز دارند. در صورتی که تابع، هیچ آرگومانی نداشته باشد این پرانتزها مانند مثال زیر خالی خواهند بود:

1func doStuff() { /* doing stuff */ }

منظور از آرگومان متغیرهایی هستند که می‌توان به تابع ارسال کرد. اگر تابع ما آرگومانی داشته باشد در این صورت آن‌ها را در اعلان تابع می‌آوریم. در مثال فوق ما دو آرگومان داریم که به صورت number و otherNumber مشخص شده‌اند. این آرگومان‌ها چندان گویا نیستند؛ اما شما باید سعی کنید در انتخاب نام‌ها از انواع توصیفی و گویا استفاده کنید.

ممکن است متوجه شده باشید که از number firstNumber و otherNumber secondNumber استفاده کرده‌ایم. ما در اجراهای بعدی این دو متغیر از number و otherNumber استفاده می‌کنیم و بدین ترتیب متوجه خواهیم شد که مشغول تعیین مقدار برای کدام آرگومان هستیم. در واقع نام‌های firstNumber و secondNumber آن چیزهایی هستند که ما در بدنه تابع خود مورد استفاده قرار می‌دهیم. بدنه تابع به همه کدی گفته می‌شود که بین آکولاد باز و بسته ارائه می‌شود:

1{ /* this is the body */ }

در ادامه بخشی به صورت number firstNumber: Int داریم. Int نوع داده‌ای است که انتظار داریم در هنگام فراخوانی این تابع از آن استفاده کنیم. با استفاده از number firstNumber: Int اعلام می‌کنیم که نام آرگومان نخست number است و متغیری که در تابع برای آن استفاده خواهیم کرد firstNumber و نوع آن Int است. همین موضوع در مورد otherNumber secondNumber: Int نیز صدق می‌کند.

در نهایت یک ساختار عجیب به صورت -> Int در انتهای اعلان تابع و خارج از پرانتزها داریم. این کاراکترها صرفاً برای نمایش محل پایان تابع استفاده می‌شوند و نشان می‌دهند که تابع ما باید یک مقدار صحیح بازگشت دهد. منظور از بازگشت (return) این است که وقتی تابع به پایان می‌رسد، باید مقداری را در خروجی ارائه دهد. در تابع مثال فوق ما یک ثابت دارای نام به صورت functionResult ایجاد می‌کنیم که مجموع firstNumber و secondNumber است و سپس با عبارت return functionResult این مجموع را بازگشت می‌دهیم.

در واقع وقتی ما این تابع را فراخوانی می‌کنیم، از کد ...  = var result استفاده می‌کنیم، چون می‌دانیم که این تابع قرار است متغیر یا ثابت بازگشت دهد و باید آن را ذخیره کنیم. اگر قرار بود تابع ما چیزی بازگشت ندهد، کد فوق با خطا مواجه می‌شد. همچنین در صورتی که قرار بود تابع ما مقداری بازگشت دهد؛ اما ما این مقدار بازگشتی را در متغیری ذخیره نمی‌کردیم، با هشدار عجیب زیر مواجه می‌شدیم.

1result of <functionName> is unused, consider using _ =.

شاید از خود بپرسید منظور از  =_ در انتهای پیام فوق چیست؟ در زبان سوئیفت کاراکتر زیرخط به معنی مواردی است که شما توجه نداشته باشد. یعنی یا توجه نکرده‌اید که چه مقداری بازگشت یافته است و یا به استفاده از نام آرگومان برای متغیر اول در تابع خود اهمیت نداده‌اید.

دقت کنید که شما می‌توانید به نام آرگومان نخست اشاره نکنید. این وضعیت به صورت زیر است:

1func addTwoNumbers(_ firstNumber: Int, otherNumber secondNumber: Int) -> Int {
2    return firstNumber + secondNumber
3}
4
5// calling the function
6var result = addTwoNumbers(5, otherNumber: 3)

این روش کدنویسی زیباتر است. چون ما صرفاً مجموع را بازگشت می‌دهیم و هیچ منطق دیگری را روی آرگومان‌های ورودی اجرا نمی‌کنیم. از این رو می‌توانیم از کد زیر استفاده کنیم:

1return firstNumber + secondNumber

همچنین اگر بخواهیم نام‌های گویاتری برای آرگومان‌های خود انتخاب کنیم، می‌توانیم از کد زیر بهره بگیریم:

1func addTwoNumbers(_ firstNumber: Int, secondNumber: Int) -> Int {
2    return firstNumber + secondNumber
3}
4
5var result = addTwoNumbers(5, secondNumber: 3)

در کد فوق می‌بینیم که firstNumber از یک کاراکتر زیرخط (_) استفاده کرده است و از این رو یک ورودی داریم که می‌توانیم عدد نخست خود را در پارامترها درج کنیم. secondNumber نام آرگومان ندارد و از این رو به صورت پیش‌فرض از نام متغیر استفاده می‌شود. مهم‌تر از آن این است که این کد همچنان برای ما با معنی است.

برای این که مطمئن بشوید همه مفاهیم مطرح شده را به درستی آموخته‌اید، به سؤال زیر پاسخ بدهید. کد زیر چه کاری انجام می‌دهد:

1func add(firstNumber firstNumber: Int, to secondNumber: Int) -> Int {...}

این تابع را چگونه می‌توان فراخوانی کرد؟ آیا این تابع همان‌طور که خوانده می‌شود عمل می‌کند؟ شاید این یک مثال عالی نباشد؛ اما به ما نشان می‌دهد که شفافیت کد به خصوص در تابع‌ها می‌تواند به بهبود خوانایی کد کمک زیادی بکند.

بنابراین همان طور که دیدید ما می‌توانیم یک تابع را به روش‌های مختلفی بنویسیم که همه آن‌ها هم صحیح هستند. شما کنترل زیادی روی روش خوانده شدن، و نام‌های آرگومان و متغیرهای مورد استفاده در تابع‌ها دارید.

گزاره print به صورت ("!print("Hello, World در واقع یک تابع است که یک مقدار رشته‌ای بازگشت می‌دهد. ما از آن برای نمایش متغیرها در کنسول (console) استفاده می‌کنیم. کنسول را می‌توان مانند یک پنجره صرفاً خواندنی یا پنجره اعلان فرمان تصور کرد. نکته جالب در مورد تابع print این است که این تابع از شیوه cast کردن یا تبدیل یک نوع به رشته اطلاع دارد. مثلاً می‌تواند مقدار صحیح و یا حتی نوع دیکشنری را به رشته تبدیل کند. با این که این تابع همیشه زیبا به نظر می‌رسد؛ اما در زمان نوشتن برنامه‌ها به خصوص در هنگام دیباگ کردن به طور گسترده‌ای مورد استفاده قرار می‌گیرد. دیباگ کردن یک برنامه روش دیگری برای بیان این وضعیت است که چیزی اشتباه شده است و اینک تلاش می‌کنیم بفهمیم که اشکال در کجای برنامه است. در مورد دیباگ کردن در بخش‌های بعدی این سری مطالب بیشتر صحبت خواهیم کرد؛ اما در حال حاضر صرفاً به توضیح گزاره print برای فهمیدن محل اشکال در برنامه می‌پردازیم.

1func add(_ firstNumber: Int, to: secondNumber) -> Int {
2    return firstNumber + secondNumber
3}
4
5let result = add(5, to: 25)
6
7print(result)                // displays "30" in the console

از این به بعد در کدهای خود از تابع ("print("something به جای کامنت "result is equal to "somethin// استفاده خواهیم کرد، زیرا در برنامه‌های واقعی از همین روش برای فهمیدن موارد مختلف استفاده می‌شود.

لزومی نیست که تابع‌ها حتماً مقداری بازگشت دهند، به جای آن می‌توانیم از گزاره print تو در تو برای مدیریت گزارش خطا به صورت زیر استفاده کنیم:

1func somethingWentWrong() {
2    print("Aww...")
3}
4
5if a < b {
6    if sunIshShining {
7        if madeCupOfCoffee {
8           print("Yay!")
9        } else {
10           somethingWentWrong()
11        }
12    } else {
13        somethingWentWrong()
14    }
15} else {
16    somethingWentWrong()
17}

اینک ما تابع ("...print("Aww را انتخاب کرده و آن را درون یک تابع کپسوله‌سازی می‌کنیم تا هر زمان که دوست داشتیم، مورد استفاده قرار دهیم. این کار تجزیه (decomposition) نام دارد. اگر ترکیب (composition) به طراحی طرز کار یک کد گفته شود، تجزیه، استخراج کدی است که بارها و بارها استفاده می‌شود و بدین ترتیب ارجاع به آن آسان‌تر می‌شود. برای این مثال ساده ما می‌توانیم صرفاً یک گزاره print ساده بنویسیم، زیرا تنها یک خط است؛ اما با توجه به مقاصد آموزشی همین مقدار کد نیز برای درک کار تابع‌ها مفید است.

در این تابع از آنجا که هیچ مقداری بازگشت نمی‌یابد، هنگامی که به پایان برسد لازم نیست از کلیدواژه return استفاده کنیم. البته ما می‌توانیم از return استفاده کنیم؛ اما در آن زمان وقتی همه کد درون بدنه تابع اجرا شد و به انتهای آکولادها رسیدیم، تابع هیچ چیز بازنمی‌گرداند. تنها زمانی که احتمالاً باید از کلیدواژه return برای تابعی که مقداری بازگشت نمی‌دهد، استفاده کرد، هنگامی است که می‌خواهیم قبل از آن که همه کد اجرا شود از تابع خارج شویم.

نکته: اگر گزاره return را بدین صورت قبل از پایان تابع و خارج از یک بررسی شرطی قرار دهید، کامپایلر متوجه می‌شود که کد ادامه آن هرگز اجرا نخواهد شد و برای نمونه Xcode هشداری می‌دهد که این وضعیت را طوری اصلاح کنید که یا کدهای بعد از return پاک شوند و یا آن کدها نیز شانس اجرا شدن بیابند.

1func printDisposition(_ isSunny: Bool) {
2    if isSunny {
3        print("Yay!")
4        return              // early exits from the method
5    }
6  
7    print("Aww...")        // if it is sunny the program will never
8                           // reach this code.
9}

Enumerations یا Enums

Enumerations

تصویر فوق نمایی از ماهیت یک enum را نشان می‌دهد. enum-ها گزینه‌هایی برای برنامه محسوب می‌شوند. هر enum مجموعه‌ای از آیتم‌های مرتبط را نمایش می‌دهد که تنها یکی از آن‌ها انتخاب می‌شود. شیوه تعریف آن چنین است:

1enum Stoplight {
2   case go
3   case slowDown
4   case stop
5}

تاکنون احتمالاً متوجه شده‌اید که در برنامه‌نویسی سوئیفت از حالت نامگذاری شتری (camelCase) برای متغیرها استفاده کردیم. یادگیری نامگذاری در حالت شتری بسیار آسان است، کافی است یک عبارت را انتخاب کنید، فواصل را حذف کنید، حرف اول کلمه اول را به صورت کوچک و سپس حروف اول همه کلمات بعدی را به صورت بزرگ بنویسید. برای نمونه اگر بخواهیم با استفاده از عبارت «ice cream flavor» یک نام متغیر بسازیم، نتیجه کار در حالت شتری «iceCreamFlavor» خواهد بود. اما enum مربوطه به صورت «IceCreamFlavor» نامگذاری می‌شود.

Enum شبیه به func است و به کامپایلر اعلام می‌کند که ما می‌خواهیم یک enum به نام Stoplight تعریف کنیم؛ اما این بار هیچ آکولاد باز یا بسته برای بدنه enum نداریم؛ بلکه از حالت‌ها (cases) برای هر یک از گزینه‌هایی که enum ارائه می‌کند، استفاده می‌کنیم.

در یک چراغ راهنمایی ما یا یک چراغ سبز به مفهوم «حرکت»، یا یک چراغ زرد به معنی «حرکت با احتیاط» و یا یک چراغ قرمز به معنی «توقف» داریم. در یک چراغ راهنمایی هرگز بیش از یک حالت روشن نیست. این enum نیز می‌توان برای ارائه گزینه‌هایی استفاده کرد که یا مقداری دارند و یا بولی هستند. در مورد روش استفاده از enum با استفاده از مقدار در ادامه صحبت خواهیم کرد؛ اما فعلاً به معرفی گزینه‌های بولی می‌پردازیم.

1enum Stoplight {
2    case go, slowDown, stop
3}
4
5var trafficStatus: Stoplight = Stoplight.go
6var trafficHasMovedForOneMinute = false
7var secondsPassed = 0
8var timeLightChanged = 0
9
10while !(trafficStatus == Stoplight.stop) {
11    if secondsPassed == 60 {
12        trafficHasMovedForOneMinute = true
13    }
14    
15    if trafficHasMovedForOneMinute &&
16        trafficStatus != Stoplight.slowDown {
17        trafficStatus = Stoplight.slowDown
18        timeLightChanged = secondsPassed
19    }
20    
21    if trafficStatus == .slowDown &&
22       (secondsPassed > timeLightChanged) {
23        trafficStatus = .stop
24    }
25    
26    secondsPassed += 1
27}

به بررسی کد فوق می‌پردازیم. ابتدا enum خود را تعریف می‌کنیم و نکته خاصی در این جا وجود ندارد. یک متغیر به نام trafficStatus از نوع Stoplight ایجاد کرده و مقدار اولیه آن را به صورت Stoplight.go تعیین می‌کنیم. سپس یک گزینه بولی برای بررسی این که آیا 60 ثانیه سپری شده یا نه می‌سازیم. به خاطر داشته باشید که اگر این برنامه را در عمل اجرا کنیم در کمتر از چند نانوثانیه به پایان می‌رسد. سپس یک شمارشگر برای تعداد ثانیه‌های سپری شده می‌سازیم و متغیری اعلان می‌کنیم که زمان سپری شده از تغییر چراغ را ذخیره می‌کند.

در مرحله بعد به گزاره While می‌رسیم و عبارت (while!(trafficStatus == Stoplight.stop را ارز‌یابی می‌کنیم. از این رو می‌دانیم که برنامه چه هنگام به پایان می‌رسد. نخستین چیزی که باید بررسی کنیم این است که آیا if secondsPassed == 60 به صورت true در آمده است یا نه، چون باید مقدار بولی trafficHasMovedForOneMinute را تعیین کنیم.

سپس بررسی می‌کنیم که آیا if trafficHasMovedForOneMinute && trafficStatus!= Stoplight.slowDown به صورت true در آمده است یا نه. بنابراین هنگامی که زمان آن برسد می‌توانیم مقدار trafficStatus = Stoplight.slowDown را تنظیم کنیم. این امر به ما اجازه می‌دهد که مقدار timeLightChanged را نیز برابر با تعداد ثانیه‌های سپری شده تعیین کنیم. باید بررسی کنیم تا مطمئن شویم که trafficStatus قبلاً بدین صورت تعیین نشده باشد، چون در این صورت به طور مداوم timeLightChanged به‌روزرسانی خواهد شد.

سپس به بخشی می‌رسیم که باید if trafficStatus ==.slowDown و secondsPassed > timeLightChanged تعیین شود و سپس مقدار trafficStatus =.stop تعیین شود. می‌توان از slowDown. و stop. به دلیل اینترفیس نوع سوئیفت استفاده کرد. کامپایلر می‌داند که trafficStatus از نوع Stoplight است و از این رو می‌توانیم تنها از عملگر نقطه (.) استفاده کنیم تا به هر حالتی که می‌خواهیم دست پیدا کنیم.

در نهایت secondsPassed را یک ثانیه بالاتر تعیین می‌کنیم تا برای اجرای بعدی آماده باشد. بنابراین این حلقه 60 بار اجرا می‌شود تا این که trafficHasMovedForOneMinute به‌روزرسانی شود. سپس بی‌درنگ مقدار trafficStatus ==.slowDown را تغییر می‌دهیم و متغیر timeLightChanged را برابر با مقدار secondsPassed تعیین می‌کنیم. در این صورت از گزاره if آخر عبور می‌کنیم، زیرا secondsPassed بزرگ‌تر از timeLightChanged نیست و هم‌مقدار هستند. ما مقدار secondsPassed را برابر با 61 تعیین می‌کنیم و سپس به اجرای ادامه کد می‌پردازیم.

نیازی برای به‌روزرسانی trafficHasMovedForOneMinute وجود ندارد، زیرا مقدار آن قبلاً به صورت true تعیین شده است و می‌توانیم از نخستین گزاره if رد شویم. ما از گزاره if دوم نیز رد می‌شویم هر چند trafficHasMovedForOneMinute به مقدار true تعیین شده است، زیرا trafficStatus ==.slowDown است. در نهایت بررسی نهایی به صورت if trafficStatus ==.slowDown انجام می‌گیرد که true است و با توجه به این که secondsPassed > timeLightChanged نیز true است، مقدار trafficStatus =.stop را تنظیم می‌کنیم. برای آخرین بار secondsPassed را به‌روزرسانی می‌کنیم و حلقه while از نو آغاز می‌شود. در واقع این کد بی‌درنگ متوجه می‌شود که چراغ راهنمایی قرمز شده است و حلقه متوقف می‌شود.

این مثال را می‌توان طوری بسط داد که واقع‌گرایانه‌تر باشد و به این منظور می‌توان از متغیرهای trafficLight1, trafficLight2, trafficLight3 و trafficLight4 استفاده کرد و طراحی آن را به صورتی انجام داد که حلقه while مانند یک چراغ راهنمایی معمولی هرگز پایان نیابد. همچنین باید یک متغیر بولی به نام carIsPresent اضافه کنیم که تشخیص می‌دهد خودرو هنگام رسیدن به چراغ راهنمایی با کدام نوع چراغ مواجه شده است. در این حالت اگر متغیری به نام carHasBeenWaitingFor30Seconds داشته باشیم، می‌توانیم چراغ را مجبور کنیم که زودتر عوض شود. این کارکرد را می‌توان به تابع‌های جدیدی تقسیم کرد؛ اما انجام این کدنویسی را به عنوان یک تمرین به شما واگذار می‌کنیم.

در بخش قبلی این سری مطالب به شما گفتیم که در این مطلب به معرفی عملگر نقطه (.) خواهیم پرداخت. عملگر نقطه برای دسترسی به عناصر درون انواع استفاده می‌شود. این عناصر به صورت‌های رشته، عدد صحیح (Integer)، عدد صحیح با دقت مضاعف (Double)، عدد اعشاری (Float)، دیکشنری‌ها، آرایه‌ها و مقادیر بولی هستند. همچنین به صورت Structs و Classes نیز وجود دارند؛ اما این انواع را در بخش‌های بعدی بررسی خواهیم کرد.

عملگر نقطه امکان دسترسی به تابع‌ها و متغیرهای درون انواع را نیز می‌دهد. به مثال زیر توجه کتید:

1var myArray: [Int] = [1, 4, 6, 2, 4, 7, 8]
2
3print(myArray.count)     //prints 7 to the console
4print(myArray.reversed()) // prints [8, 7, 4 ,2 ,6, 4, 1] to the  
5                          // console

آرایه‌ها دارای متدهای درونی هستند که امکان اجرای کارهای مختلف روی آرایه‌ها را می‌دهد. برای نمونه myArray.count تعداد کل عناصر موجود در آرایه را نمایش می‌دهد. ()myArray.reversed یک متد را درون آرایه فراخوانی می‌کند که ترتیب آرایه مفروض را معکوس می‌کند.

متد تنها یک نام دیگر برای تابعی درون یک شیء است. در واقع از هر نظر که نگاه کنیم با توجه به مقاصدی که از این راهنما داریم، متدها و تابع‌ها ظاهر یکسانی دارند و عملکردشان نیز مشابه است. توسعه‌دهندگان زیادی از این دو نام به اشتباه به جای هم استفاده می‌کنند؛ اما اغلب از این اشتباه آگاه نیستند و از این رو مسئله مهمی محسوب نمی‌شود.

گزاره‌های سوئیچ (switch) و enums معمولاً با همدیگر استفاده می‌شوند. در واقع گزاره سوئیچ را می‌توان جایگزینی برای یک گزاره if واقعاً طولانی تصور کرد.

1enum Stoplight {
2    case .go
3    case .slowDown
4    case .stop
5    case .notWorking
6}
7
8var trafficLight: Stoplight = .go
9var badDriver = false
10
11switch trafficLight {
12case .go:
13    driveForward()
14    
15case .slowDown:
16    if badDriver {
17        driveFaster()
18    } else {
19        slowDown()
20    }
21    
22case .stop:
23    slowDownAndStop()
24    
25default:
26    treatIntersectionAsStopSign()
27}

گزاره سوئیچ با استفاده از <switch <variableToCheck تعریف می‌شود. سپس یک آکولاد باز داریم که آغاز بدنه گزاره سوئیچ را نشان می‌دهد. به جای نوشتن if trafficLight ==.go برای هر شرایط احتمالی، کافی است از case-ها برای تعریف آن چه به دنبالش هستیم استفاده کنیم. سوئیفت به قدر کافی هوشمند است که تشخیص دهد trafficLight یک کانتینر از نوع Stoplight است و می‌تواند استنباط کند که هر بار از یک گزاره حالت (case) استفاده می‌کنیم به برسی مقادیر احتمالی که می‌تواند برای trafficLight استفاده شود می‌پردازیم. گزاره case باید با یک کلیدواژه case آغاز شود و باید یک مقدار.go داشته و با دونقطه (:) پایان یابد. برای هر حالت از آکولاد استفاده نمی‌شود؛ اما اگر از حالت پیشفرض :default استفاده نکنید، باید یک حالت برای همه گزینه‌های ممکن در enum وجود داشته باشید. حالت :default برای همه شرایط دیگر که تعریف نشده‌اند استفاده می‌شود.

از آنجا که سوئیفت می‌تواند از اعداد و بازه‌هایی از اعداد استفاده کند، می‌توانیم یک گزاره سوئیچ بدون enum راه‌اندازی کنیم. در ادامه طرز کار یک مثال را با استفاده از بازه‌های سنی می‌بینیم:

1var age: Int = 26
2
3switch age {
4case (..< 0):
5    print("I am not born yet!")
6  
7case 0:
8    print("Hello, World!")
9  
10case (1...12):
11    print("I am young!")
12  
13case (13...17):
14    print("I am a teenager!")
15  
16case 18:
17    print ("I am an adult and able to vote!")
18  
19default:
20   print("I am an adult.")
21}

در مثال فوق، ابتدا با یک بازه نیم‌باز آغاز می‌کنیم، اگر سن فرد کمتر از 0 باشد، عبارت «هنوز متولد نشده‌ام» (I am not born yet) را نمایش می‌دهیم. گزینه بعدی case :0 صرفاً بررسی می‌کند که آیا سن فرد برابر با 0 است یا نه، اگر چنین بود، کد زیر اجرا می‌شود. سپس بررسی می‌کنیم که آیا سن فرد در بازه‌های 12...1 و یا 17...13 قرار دارد یا نه و برای هر یک بر مبنای سن مربوطه عبارت‌هایی نمایش می‌دهیم. اگر سن فرد 18 باشد به وی تذکر می‌دهیم که یک بزرگسال است و می‌تواند رأی بدهد. از آنجا که همه بازه‌های سنی مورد نظرمان را بررسی کرده‌ایم، می‌توانیم از حالت :default برای پوشش همه سنین دیگر استفاده کنیم. گزاره‌های سوئیچ قدرتی به مراتب بیش‌تر از آنچه در این مثال مشاهده کردید دارند و در موارد دیگر می‌توان از آن‌ها به روشی بهتر و بزرگ‌تر استفاده کرد.

Scope

دامنه

بخش نهایی این قسمت از سری مطالب آموزش برنامه‌نویسی سوئیفت به دامنه (scope) اختصاص دارد. دامنه در enum-ها، تابع‌ها، گزاره‌های if، حلقه‌های while و حلقه‌های for وجود دارد. به طور کلی ما همه این مفاهیم را بررسی کرده‌ایم؛ اما منظور از این دامنه چیست؟

دامنه مشخص می‌کند که یک شیء در کدام مناطق کد زنده است.

البته منظور ما از این که می‌گوییم یک شیء زنده است به هیچ وجه حیات فیزیکی نیست، بلکه منظورمان زنده بودن در مفهوم برنامه‌نویسی است یعنی می‌توانیم از آن شیء بخواهیم تا کاری برای ما انجام دهد. دلیل این که از این عبارت استفاده می‌کنیم آن است که اشیا در برنامه یک چرخه عمر دارند. برای نمونه در اغلب موارد از زمانی که متغیرها در آغاز برنامه تعریف می‌شوند تا زمانی که برنامه خاتمه یابد، آن‌ها را زنده تصور می‌کنیم.

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

مسئله این است که ما نمی‌توانیم همه چیز را وارد حافظه بکنیم. فضای حافظه محدود است و از این رو ما باید در مورد این که در هر لحظه چه چیزهایی در برنامه در دسترس خواهند بود، دست به انتخاب بزنیم و در واقع دامنه آن‌ها را تعریف بکنیم.

بدین ترتیب با ماهیت دامنه آشنا شدیم؛ اما در ادامه با ارائه یک مثال عملی از شیوه تعریف کردن دامنه و استفاده از آن در کد و همچنین آشنایی با این که چه تأثیری بر روی مکان تعریف کردن متغیرها دارد با آن بیشتر آشنا می‌شویم.

1enum MyEnum {
2    case .hasCoffee
3    case .hasTea
4}
5
6var myGlobalInteger = 1
7
8func myGlobalFunction() {
9    var name: String = "Jimmy"
10    print(name)
11}
12
13func myGlobalReturningFunction() -> String {
14    return "Katy"
15}
16
17var name: String?
18
19if (name = myGlobalReturningFunction()) == "Katy" {
20   print(name)
21else {
22   let myMagicNumber = 42
23   var myCup = MyEnum.hasTea
24   print(myGlobalInteger)
25   print(myGlobalFunction())
26}

بدین ترتیب در کد فوق همه انواع ممکن برای دامنه را تعریف کردیم. توضیح آن به شرح زیر است:

myGlobalInteger یک شیء است که می‌توان تا زمانی که برنامه در حال اجرا است، در هر کجای کد استفاده کرد.

myGlobalFunction تابعی است که می‌تواند تا زمانی که برنامه در حال اجرا باشد، در هر کجای کد استفاده شود؛ اما متغیر name درون آکولادها تنها درون این آکولادها قابل استفاده است. به محض این که تابع به وجود بیاید، متغیر name همراه با مقدارش از حافظه پاک می‌شود.

myGlobalReturningFunction دقیقاً همانند myGlobalFunction است؛ به جز این که یک مقدار بازمی‌گرداند و ممکن است تصور کنید که متغیر ایجاد شده درون آکولادها را بازگشت می‌دهد، زیرا این طور به نظر می‌رسد؛ اما در واقع تنها مقدار آن متغیر را بازگشت می‌دهد. زمانی که تابع به آکولاد نهایی خود برسد، متغیر name از حافظه حذف می‌شود و ما تنها یک رونوشت از آن داریم که خوشبختانه به متغیر دیگری انتساب یافته است.

?var myName: String به صورت عامدانه به طور اختیاری تعریف شده است؛ اما همچنان در همه بخش‌های مثال فوق در دسترس است.

گزاره if از این نظر جالب است، چون از چیزی که قبلاً دیدیم یعنی if (expression) == value استفاده کرده‌ایم. ابتدا گزاره if اجرا می‌شود و زمانی که به پرانتزها برسیم، بنا بر ترتیب عملیات، قبل از هر چیزی، کار درون پرانتز اجرا می‌شود.

name = myGlobalReturningFunction تعیین می‌شود و سپس بقیه عبارت اجرا می‌شود. از آنجا که می‌دانیم تابع بازگشتی مقدار "Katy" را بازگشت می‌دهد، این گزاره if باید مقایسه "if "Katy" == "Katy را اجرا کند و از آنجا که هر دو مقدار یکسان هستند ما به متغیر name سراسری اختیاری از درون گزاره if ارجاع می‌دهیم و مقدار را از آنجا می‌گیریم (چون یک مقدار را هنگام اجرای ارزیابی انتساب داده‌ایم).

با این وجود، فرض می‌کنیم name بازگشتی چیزی مانند "Sophie" و متفاوت از "Katy" است. در گزاره else یک ثابت و یک متغیر وجود دارد که ایجاد و مقداردهی اولیه شده‌اند. این دو تنها در حالتی استفاده می‌شوند که درون آکولادهای گزاره else باشیم. اگر به این جا برسیم یک متغیر داریم که در بخش if گزاره if ایجاد شده و تنها می‌تواند در بخش if استفاده شود.

ما همچنان می‌توانیم myGlobalVariable و myglobalFunction را نمایش بدهیم، زیرا در خارج از آکولادها تعریف شده‌اند.

MyEnum نیز وضعیت مشابهی دارد چون حالت‌ها تنها می‌توانند درون MyEnum استفاده شوند و این وضعت شبیه به MyEnum.hasCoffee است. ما نمی‌توانیم از.hasCoffee به صورت مستقل استفاده کنیم؛ مگر این که نوع آن استنباط شود، زیرا خارج از دامنه‌اش قرار دارد. منظور از استنباط نوع این است که var myCup: MyEnum می‌داند که تنها مقداری که می‌تواند داشته باشد چیزی است که از MyEnum می‌گیرد و از این رو آن را برابر با آن قرار می‌دهیم. کامپایلر این مقدار را برای شما می‌نویسد و از این رو به صورت var myCup = MyEnum.hasCoffee است.

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

جمع‌بندی

در این نوشته در ابتدا به بررسی 3 اصلی پرداختیم که به تصور کردن شیوه نمایش کد کمک می‌کنند و شامل جداسازی دغدغه‌ها، اصل مسئولیت منفرد و عدم تکرار کد می‌شوند.

سپس در مورد تابع‌ها و این که گونه به اجرای این اصل‌ها کمک می‌کنند صحبت کردیم. شدیداً توصیه می‌کنیم که با نوشتن تابع‌های خودتان به تمرین در این خصوص بپردازید تا بتوانید ماهیت آن‌ها را شناسایی کرده و موارد مجاز و غیرمجاز را بشناسید. همچنین مطالبی در مورد شیوه خروج زودهنگام از تابع با استفاده از return بیان شد.

در ادامه به مبحث enum-ها پرداختیم، زیرا کارایی زیادی دارند و مواردی فراتر از درست/نادرست را در اختیار ما قرار می‌دهند. البته enum-ها کارکردهای مفید زیادی دیگری نیز دارند و در بخش‌های بعدی به آن‌ها بیشتر خواهیم پرداخت.

در نهایت در مورد دامنه صحبت کردیم. دامنه در کدها بسیار حائز اهمیت است و در اغلب موارد عامل ایجاد باگ در کد است. اگر دامنه متغیر به درستی تعریف نشود، موجب بروز اشکال‌ها و باگ‌های زیادی می‌شود. نکته مهم در این خصوص آن است که متغیرها باید در کمترین دامنه ممکن اعلان شوند، اما اگر متغیر در جاهای دیگر هم نیاز بود می‌توانید اعلان آن را به دامنه‌های بالاتر نیز ببرید. در ادامه سری مطالب آموزش برنامه‌نویسی سوئیفت در خصوص این موضوع نیز توضیحات بیشتری ارائه خواهیم کرد.

در بخش بعدی این سری مقالات سوئیفت در مورد Struct-ها، کلاس‌ها، مشخصات و متدها صحبت می‌کنیم.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
swift2go
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *