آموزش برنامه نویسی سوئیفت (Swift): اسامی مستعار نوع (Type Aliases) — بخش دوازدهم
در بخش قبلی این سری مقالات آموزش سوئیفت با مفاهیم بستار و Grand Central Dispatch آشنا شدیم. با این که این مفاهیم تا حدودی دشوار بودند، اما نکته خوب ماجرا این است که اکنون بخش دشوار را پشت سر گذاشتهایم. در این مقاله به معرفی مفاهیم جدیدی مانند اسامی مستعار نوع میپردازیم که به خواناتر ساختن کد و کاهش اندازه کد کمک میکنند. همچنین با تفاوت Self و self به جز کوچک/بزرگ بودن حرف اول آشنا میشویم.
اسامی مستعار نوع
اسامی مستعار نوع کمک میکنند که کدهای خود را خواناتر بنویسیم و وظایف روزمرهمان به عنوان یک برنامهنویس سادهتر شود. در زبانهای برنامهنویسی دیگر این مفهوم ممکن است به صورت alias یا typedef باشد؛ اما در سوئیفت از typealias استفاده میکنیم.
یک typealias در واقع تغییر نامی برای یک نوع است به طوری که درک یا استفاده از آن سادهتر باشد. شما در کد خود میتوانید از typealias برای دریافت یک نوع خام و تغییر دادن نام آن به چیز دیگر استفاده کنید تا معنی بهتر انتقال یابد. به مثال زیر توجه کنید:
1struct Book {
2 let pageContents: [Int: String]
3 var currentPage: Int
4
5 func getContents(on page: Int) -> String {
6 return pageContents[page]
7 }
8}
خواندن این کد چندان دشوار نیست؛ پیگیری مواردی که در این کد ارائه شدهاند کاملاً آسان است؛ اما در نگاه نخست ممکن است تعجب کنید که pageContents چیست و چرا نوع آن به صورت [Int:String] است. چرا نوع آن صرفاً String نیست؟ چون ما صرفاً به محتوای صفحه اشاره میکنیم؟ پاسخ این است که کتابها چندین صفحه دارند و باید متوجه شویم که آیا در صفحه صحیحی قرار داریم یا نه. به همین دلیل است که یک دیکشنری به نام pageContents و با نوع [Int: String] میسازیم.
اما نکته مهمتر این است که آیا ما در مورد استفاده از [Int: String] پس از این نتیجهگیری تأمل میکنیم یا این که ممکن است به تازگی با این پروژه آشنا شده باشیم و برای اولین بار آن را میبینیم. این همان جایی است که اسامی مستعار نوع به کار میآیند. کد فوق را با استفاده از اسامی مستعار نوع به صورت زیر بازنویسی میکنیم:
1typealias index = Int
2typealias content = String
3typealias page = [index: content]
4
5struct Book {
6 let pageContents: page
7 var currentPage: index
8
9 func getContents(on page: index) -> content {
10 return pageContents[index]
11 }
12}
بدین ترتیب همه نوعهای استاندارد را که در این چارچوب معنیدار هستند جایگزین میکنیم. در پسزمینه طرز کار کد دقیقاً همانند کد قبلی است؛ اما از منظر ما اینک همه آنها در یک چارچوب قرار گرفتهاند.
JSON
چیزی که باید در مورد اسامی مستعار نوع دقیقاً همانند متغیرها بدانیم، این است که آنها نیز تحت سلطه «دامنه» (Scope) قرار دارند. اگر یک نام مستعار نوع را در یک کلاس یا struct تعریف کنید نمیتوانید خارج از آن کلاس یا struct از آن استفاده کنید، چون با خطایی مواجه میشوید که Xcode اعلام میکند در مورد typealiasName اطلاع ندارد.
بنابراین مثالی دیگری را با استفاده از مفهومی نسبتاً فنیتر ارائه میکنیم و به توضیح JSON میپردازیم. JSON اختصاری برای عبارت «نمادگذاری شیء جاوا اسکریپت» (JavaScript Object Notation) است. البته نباید نگران شوید، زیرا قصد نداریم در مورد جاوا اسکریپت صحبت کنیم و صرفاً مقدمهای در مورد JSON ارائه خواهیم کرد.
JSON به طور مقدماتی در وبسرویسها برای ارسال دادهها بین کلاینتها و سرورها مورد استفاده قرار میگیرد و هدف اصلی آن ذخیرهسازی دادهها روی یک دستگاه محلی به روشی ساده برای تحلیل و نگهداری است. JSON به طور معمول مانند زیر است:
1{
2 "breakfast": {
3 "item1": "eggs",
4 "item2": "bacon",
5 "item3": "toast"
6 }
7}
8
9// all one line
10{"breakfast":{"item1":"eggs","item2":"bacon","item3":"toast"}}
اگر آن را به زبان سوئیفت ترجمه کنیم به شکل زیر در میآید:
1["breakfast":["item1":"eggs","item2":"bacon","item3":"toast"]]
2
3//or
4[String: [String: String]]
دقت کنید که ما صرفاً آکولادها را با براکت عوض کردهایم. ما این نوع جدید را به نام [String: Any] میشناسیم و هر سطح از آن را به هر شیئی که نیاز باشد تبدیل میکنیم. برای درک بهتر به مثال زیر توجه کنید:
1let booksJSON = data as! [String: Any]
2
3let book = booksJSON["Dr. No"] as! [String: Any]
4
5let characters = book["Characters"] as [String: Any]
6
7let jamesBond = characters["MainCharacter"] as! String
8
9let sideKicks = characters["SideKick"] as! [String]
10
11let honey = sideKicks[0] as! String
12
13let quarrel = book["Characters"]["SideKick"][1] as! String
برای نمونه خود JSON چیزی مانند زیر است:
1{
2 "Dr. No": {
3 "Characters": {
4 "MainCharacter": "James Bond",
5 "SideKick": ["Quarrel", "Honeychile Rider"],
6 "Villian": "Dr. No",
7 "Henchmen": ["Professor R.J. Dent", ... ]
8 }
9 },
10 "Goldfinger": {
11 ...
12 }
13}
اینک اگر به مثال خود بازگردیم، میتوانیم کد فوق را طوری بازسازی کنیم که دیگر چندان نیازی به [String: Any] نداشته باشیم.
1typealias jsonData = [String:Any]
2typealias book = [String: Any]
3typealias sideKickList = [String]
4typealias bookCharacter = String
5
6let bookJSON = data as! jsonData
7
8let characters = bookJSON["Characters"] as! jsonData
9
10let book = bookJSON["Dr. No"] as! book
11
12let jamesBond = characters["MainCharacter"] as! bookCharacter
13
14let sideKicks = characters["SideKick"] as! sideKickList
15
16let quarrel = sideKicks[0] as! bookCharacter
17
18let honey = sideKicks[1] as! bookCharacter
JSON و String
همان طور که میبینید این روش معنی بسیار بیشتر به کد بخشیده است. حتی زمانی که تلاش میکنیم خودمان JSON را تحلیل کنیم، میتوانیم این نوع را بر مبنای آن اسم مستعار نوع که دارد استنباط کنیم. اگر به شما گفته شود که قرار است دادههای JSON در اختیار شما قرار گیرد، شما قطعاً انتظار دارید که [String: Any] یا دستکم چیزی که با یک رشته آغاز میشود و میتواند چندین نوع داده نگهداری کند، دریافت کنید. زمانی که یک حرف از یک کتاب را میخواهیم، باید انتظار دریافت یک String را داشته باشیم. اگر به شما گفته شود که قرار ست یک لیست دریافت کنید باید انتظار یک آرایه یا چیز مشابهی را داشته باشید. در این مورد لیستی از موارد در اختیار شما قرار میگیرد و لذا باید انتشار یک آرایه از رشتهها را به صورت [String] داشته باشید.
اسامی مستعار نوع امکان جالبی هستند و قطعاً به افزایش خوانایی کمک میکنند، اما باید مواظب باشید در استفاده از آنها زیادهروی نکنید. مثلاً در مثال اخیر ممکن است کمی زیادهروی کرده باشیم، اما به طور معمول از آن در دیکشنریهایی مانند [String: Any] یا انواع بستارهایی مانند زیر استفاده میشود:
typealias completion = () -> ()
که در آنها میتوانید completion را به یک تابع بستار بفرستید و دیگر نیازی به استفاده مکرر از () <- () وجود ندارد.
Property Observers
«مشاهدهگرهای مشخصه» (Property Observers) نیز ویژگی جالبی هستند، زیرا به خودکارسازی برخی اموری که باید در زمان بهروز شدن متغیرها مداوماً رخ دهند کمک میکنند.
کار Property Observer اساساً این است که تغییرات یک مشخصه را به نظاره مینشیند و دو شکل دارد:
- willSet – این نوع پیش از تنظیم تغییرات در متغیر اجرا میشود.
- didSet – اقداماتی را پس از تغییر یافتن متغیر انجام میدهد.
شما به ندرت ممکن است بخواهید از willSet استفاده کنید و همواره از didSet استفاده خواهید کرد. مشاهدهگرهای مشخصه به صورت زیر در کد جای میگیرند:
1class StockMarket {
2 let wallet = 0 {
3 willSet {
4 // do stuff before score is set
5 }
6 didSet {
7 // do stuff after score is set
8 }
9 }
10}
همان طور که میبینید این ویژگی بسیار جالبی محسوب میشود؛ اما شاید بپرسید کاربردهای آن چیست؟ ما کلاس خود را StockMarket نامگذاری کردهایم، زیرا برای استفاده از هر دو نوع ویژگی مشاهدهگرهای مشخصه برای ما معنیدار خواهد بود. همچنین این وضعیت را فرض گرفتهایم که متدهایی در ادامه وجود دارند، زیرا در حال حاضر برای ما حائز اهمیت نیستند.
1class StockMarket {
2 typealias name = String
3 typealias amount = Int
4
5 var stocksBought = [name: amount]
6 var wallet = 100.0 {
7 // value is about to change
8 willSet {
9 updateBuyingPower(to: newValue)
10 }
11 // value did change
12 didSet {
13 getStock()
14 print("You spent $\(oldValue - wallet), you now have
15 $\(wallet) left.")
16 }
17 }
18
19 func buy(_ stock: Stock) {
20 wallet -= stock.price
21 }
22}
با این که کد ممکن است پیچیده به نظر بیاید؛ اما با توضیحی که در ادامه میدهیم برای ما روشنتر خواهد شد. ابتدا چند اسامی مستعار نوع داریم زیرا میخواهیم نشان دهیم که مثال قبلی [String: Int] به چه معنی است؛ اما [name: amount] بر روشنی آن چه که برای آن استفاده شده افزوده است.
مثال عملی
سپس عملاً از یک مشاهدهگر مشخصه به نام wallet استفاده میکنیم. در ابتدا مقدار آن را برابر با 1000 دلار تعیین میکنیم. سپس به willSet میرسیم.
willSet متغیری را در اختیار ما قرار میدهد که newValue نام دارد. این همان مقداری است که برای wallet تعیین میکنیم. در این مورد از آن برای updateBuyingPower استفاده میکنیم. این روش همانند هر فروشگاهی است که ابتدا پول را دریافت میکند و سپس کالا را تحویل میدهد.
در ادامه نوبت به مشاهدهگر مشخصه didSet میرسد. مانند مشاهدهگر willSet متغیری به نام oldValue داریم. این همان مقداری است که wallet قبلاً برای خود داشته است. بدین ترتیب اگر چیزی در getStock در مشاهدهگر مشخصه اشتباه شود میتوانیم wallet را دوباره به مقدار قبلی تعیین کنیم.
در نهایت از چیزی به نام «میانیابی رشته» (string interpolation) استفاده کردهایم که امکان استفاده از رشتهای را به ما میدهد که متغیرهایی درونش جای داده شدهاند. شما میتوانید از میانیابی رشتهای در یک رشته با استفاده از ()\ و جای دادن کد خود درون رشته استفاده کنید. این روش خلاصهای برای جایگزینی firstName + " " + lastName است. به جای آن میتوانیم از کد زیر استفاده کنیم.
"\(firstName) \(lastName)"
با این که در این مثال روش جدید کمی طولانیتر به نظر میرسد؛ اما در مثال فوق کاربرد مناسبی دارد. در آن مثال، ما حتی اندکی محاسبات روی مقدار قدیمی کیف پول نیز انجام دادهایم و مقدار کنونی کیف پول را محاسبه کردهایم تا تفاضل را حساب کرده و آن را به کاربر گزارش کنیم.
چالش جدی
بنابراین در اینجا یک چالش برای شما وجود دارد، کدی که در بخش فوق نوشتیم، یک خطا دارد.
کاربران ما تنها میتوانند سهام بخرند و هرگز نمیتوانند پول خود را باز پس بگیرند. بررسی کنید که آیا میتوانید آن را به روش خود حل کنید و به کاربران امکان بدهید که بتوانند سهام خود را به فروش نیز برسانند؟
دقت کنید که پیشتر اشاره کردیم که شما میتوانید به مقدار قبلی بازگردید. تنها مشکل این است که اگر بخواهیم دقیقاً به مقداری که قبلاً داشتیم بازگردیم وارد حلقه نامتناهی خواهیم شد. بنابراین بررسی کنید آیا میتوانید به مقدار قبلی بازگردید و در صورتی که اشکالی در فرایند خرید رخ دهد، وارد حلقه نامتناهی نشوید.