آموزش برنامهنویسی سوئیفت (Swift): پروتکل پایه در سوئیفت، اکستنشن ها و زیرنویس ها – بخش نهم
در بخش قبلی این سلسله مطالب آموزش زبان سوئیفت به بحث تبدیل نوع، باز کردن امن آپشنالها و کنترلهای دسترسی پرداختیم. در آن نوشته هیچ نکته دشواری وجود نداشت؛ اما ممکن است برای برخی از افراد که با مفهوم کنترل دسترسی آشنا نبودهاند بحث تا حدودی سنگین بوده باشد. در این نوشته یک بار دیگر به جمعبندی مفاهیم پروتکل پایه در سوئیفت و اکستنشن و همچنین زیرنویس میپردازیم، اما این بار کاربردهای عملی آنها در اپلیکیشنهای مختلف را بررسی میکنیم.
ما نهایت تلاش خود را خواهیم کرد که مسائل مختلف را به ترتیبی بررسی کنیم که بتوان از مجموع آنها نتیجهای گرفت و بدین ترتیب بدانید که از هر موضوعی در کجا و چه زمانی باید استفاده کنید. میدانیم که برخی راهنماها از این مرحله آغاز میکنند و به سرعت وارد موضوع اکستنشنها میشوند؛ اما ما چنین کاری نکردیم. اکستنشنها بلوکهای ساختمانی هستند که با آنها میتوانیم بر اساس مفاهیمی که تاکنون آموختهایم کار بیشتری انجام دهیم و به همین دلیل تصمیم داریم که کار خود را با پروتکلها آغاز کنیم.
پروتکل
میدانیم که هم پروتکل و هم اکستنشن تعریف مبهمی دارند و تعریفی که از سوی اپل ارائه شده نیز به روشنتر شدن موضوع در آغاز کار کمک چندانی نمیکند. تعریفی که در کتاب اپل آمده چنین است:
یک پروتکل به تعریف نقشه اولیهای برای متدها، مشخصهها و دیگر الزامات میپردازد که برای اجرای یک وظیفه خاص یا بخشی از یک کارکرد لازم هستند. پروتکل میتواند از سوی کلاس، سازه یا enumeration جهت ارائه پیادهسازی واقعی از این الزامات مورد استفاده قرار گیرد. هر نوعی که الزامات یک پروتکل را تأمین کند، گفته میشود که مطابق پروتکل است.
برخی از افراد ممکن است از همین تعریف نیز مفهوم مورد نظر را متوجه شوند؛ اما برخی دیگر ممکن است در این مسیر با دشواری مواجه شده باشند. در هر حال جای نگرانی نیست، در این لحظه میتوانید این تعریف را فراموش کنید، چون قصد داریم با ارائه قیاسی از بازی بیسبال، مفهوم پروتکل را به طور کامل تشریح کنیم.
در بازی بیسبال موقعیتهای مختلفی در بازی وجود دارند که هر کدام وظیفه خاصی دارند. در واقع روی زمین مکانهای مختلفی تعیین شده که هر کس در آن مکان قرار گیرد وظیفه خاصی را باید اجرا کند. ما نمیتوانیم افراد را از جاهای مختلف برداریم و وظایف متفاوتی برای آنها تعیین کنیم. در واقع وظایف و جایگاههای هر بازیکن از قبل تعریف شده است. اگر هر بازیکن را یک کلاس تصور کنیم، این که آن بازیکن چگونه بازی کند، به وسیله یک پروتکل تعریف میشود. در مثال زیر کدنویسی این مفهوم را مشاهده میکنید:
چند خط کد اضافی نیز ارائه کردهایم تا متوجه شوید که همه چیز از کجا میآید. ما در ابتدا enum-هایی برای تعریف سبکهای پرتاب توپ (pitching) تعریف کردیم. سپس یک پروتکل تعریف کردهایم. این پروتکل برای موقعیت پرتاب کننده توپ است و روشهای مختلف ممکن و یک مشخصه به نام pitchers تعریف میکند که مفید است. در ادامه یک struct به نام Pitcher ساختهایم که میتوانیم از آن برای ایجاد پرتابکنندههایی برای بازی خود استفاده کنیم. درون این سازه همه متدها و مشخصههایی که پرتابگر برای اجرای صحیح بازی نیاز دارد آمدهاند.
اما به این ترتیب ما حجم کد خود را دو برابر کردهایم. این کار عامدانه بوده است تا مطمئن شویم هر کس دیگری که روی این کد کار میکند، ملزم شود این متدها و مشخصهها را در هنگام بهکارگیری پروتکل در کد خود بگنجاند. بدین ترتیب اطمینان حاصل میشود که هر پرتابگر که از پروتکل pitcher استفاده میکند همچنان میتواند وظیفهاش را که پرتاب توپ است را اجرا کند.
با این که این وضعیت چندان مفید به نظر نمیرسد؛ اما استفاده از پروتکلها بدین ترتیب در تیمهای بزرگ بسیار مفید است. چون هیچ کس دیگری در تیم وقتی کلاسی را به صورت JSONManager نامگذاری میکنید نمیتواند بداند که کاربرد آن چیست. ممکن است این تصور مطرح شود که آیا این کلاس، دادههای JSON را گرفته و آنها را قالببندی میکند؟ آیا آنها را جایی ذخیره میکند؟ چه کار میکند؟ اگر اعلان کلاس را به صورت زیر بنویسیم:
این اعلان به شما چه میگوید؟ این اعلان مشخص میکند که JSONManager میتواند JSON را دانلود، آن را آپلود و در جای دیگری ذخیرهسازی کند یا JSON را از جایی که ذخیره شده است بازیابی کند. برای ما مهم نیست که این کلاس دادهها را کجا قرار میدهد یا از کجا بر میدارد، برای ما تنها نکته مهم این است که JSONManager قرار است به ما در اجرای این وظایف کمک کند. این کار «انتزاع» (abstraction) نامیده میشود. نکته بهتر آن است که اگر کلاس دیگری داشته باشیم که نام آن DataManager باشد، میتوانیم از برخی یا همه این پروتکلها استفاده کنیم و مطمئن باشیم که هر پروتکل که در آن به کار بگیریم، میتوانیم به وسیله آن دادههای خود را به دست بیاوریم.
هنگامی که کلاسها را مینویسیم کار خود را با کد شروع نمیکنیم؛ بلکه ابتدا تکلیف پروتکلها را مشخص میسازیم. روند کار چنین است. ما به یک پرتابگر نیاز داریم، پس چنین مینویسیم:
سپس باید فکر کنیم که پرتابگر در بازی بیسبال چه نقشی دارد؟ مشخص است که وظیفه وی پرتاب توپ است، پس مینویسیم:
اما این پروتکل خیلی ساده است، پس انواع مختلفی برای پرتاب توپ تعریف میکنیم:
میدانیم که ارسال رشتهها با استفاده از enum بسیار امنتر است، پس آن را به صورت زیر تغییر میدهیم:
بدین ترتیب تابع زیر پایان مییابد:
همچنین میدانیم که هر پرتابگری در طی پرتابهای خود، برخی پرتابهای بد نیز دارد:
و یک تابع به این منظور اضافه میکنیم:
اما از کجا بدانیم که پرتابگری پرتاب بد داشته است؟
با استفاده از این منطق، در نهایت به پروتکلی دست مییابیم که در ادامه میتوانیم طرز کار کلاسهای خود را بر مبنای آن تعریف کنیم. اپل نیز تأکید دارد که کلاسها را ابتدا با پروتکلها آغاز کنیم. ما این نکته را در بخش اول این سری آموزشهای سوئیفت نیز بیان کردیم و اینک مورد تأکید مجدد قرار میدهیم.
آغاز تعریف کلاس با پروتکل ممکن است خستهکننده و دشوار باشد، چون نتایج آنی ندارد؛ اما این امکان را به ما میدهد که همه مشخصهها و متدهای مورد نیاز برای کارکرد صحیح کلاس یا strcut را برآورد کنیم. این کار شبیه به تحلیل اپلیکیشن پیش از شروع به کدنویسی است. بدین ترتیب اگر چیزی خراب بشود اصلاح آن دشوار نخواهد بود و کافی است نام یک متد یا مشخصه در پروتکل تغییر داده شود و تنها یک یا چند خط اصلاح میشود.
همان طور که میدانیم در هر تیم تنها یک پرتابگر وجود دارد و ما میتوانیم از این struct برای ساخت دو پرتابگر استفاده کنیم. اما باید بدانیم که این متدها محدود به پرتابگرها است. در ادامه خواهیم دید که چگونه میتوانیم این کار را برای «بازیکن آوتفیلد» (Outfielder) نیز اجرا کنیم.
همان طور که دیدیم پروتکلها میتوانند ما را ملزم سازند که از کد صحیح استفاده کنیم و مهم نیست که چند بار از آن استفاده کنیم. بدین ترتیب مقاصد مورد نظر برنامهنویس و تیمش شفافتر میشوند. اما با بسط دادن پروتکلها میتوانیم بر قدرت آنها بیفزاییم.
اکستنشن
اکستنشن یک نام بزرگ برای مفهومی ساده است. اکستنشن در واقع به بسط کارکرد یک کلاس، struct یا پروتکل گفته میشود. برای این که بدانید از آنها چگونه باید استفاده کنید، ارجاعاتی به کتابخانه Foundation در سوئیفت خواهیم داشت. Foundation یک کلاس به نام UITableView دارد. کلاس UITableView وظیفهای ذاتی برای مدیریت نمایش نماهای جدولی دارد. با این وجود میتوانیم کارکرد آن را با ارائه بسطی به نام UITableViewDataSource گسترش دهیم. بدین ترتیب کلاسی ارائه میکنیم که از این پروتکل بهره میگیرد و توانایی ارائه زمینهای برای روش نمایش دادهها درون نمای جدولی مییابد.
تصویر فوق مثال مناسبی از یک اکستنشن است.
البته لزومی به بهرهگیری از یک کلاس دیگر نیست و میتوانیم صرفاً بخشهای کد را از هم دیگر به روشی منطقی جدا کنیم.
با این که ممکن است این کار تا حدودی زیادهروی محسوب شود؛ اما بدین ترتیب میتوانید از کامنت ها برای جداسازی کد به بخشهای منطقی با استفاده از دستور زیر استفاده کنید:
همچنین میتوانید با استفاده از این نشانگرها در قسمت فوقانی پنجره ویرایشگر Xcode به بخشهای مختلف کد بروید.
با کلیک کردن روی «No Selection» می توانید درخت لایه جاری را باز کنید.
بدین ترتیب میتوانیم با کلیک کردن روی هر بخش از درخت به آن بخش برویم و Xcode بیدرنگ روی آن بخش متمرکز میشود. با استفاده از دستور زیر میتوانیم یک خط افقی بالای مشخصهها ایجاد کنیم:
این وضعیت شبیه به استفاده از دستور زیر برای درج یک خط افقی زیر مشخصهها است:
همچنین میتوانیم هر دو خط بالا و پایین را درج کنیم؛ اما بهتر است تنها از یکی از آنها استفاده کنیم تا به بخشهای مشخصی تقسیم شوند. در هر حال میتوانید از رویهای که ترجیح میدهید استفاده کنید.
در این بخش تنها به یک نکته دیگر اشاره میکنیم و به بررسی موضوع اکستنشنها باز میگردیم. صرفنظر از افراز کد به بخشهای منطقی این نشانگرها یک کار دیگر نیز انجام میدهند که باعث میشود عاشق پروتکلها شوید و همه جا از آنها استفاده کنید.
بنابراین دانستیم که پروتکلها برای کار تیمی عالی هستند و این موضوع از آغاز مشخص بود؛ اما ممکن است از خود بپرسید که آیا اگر یک توسعهدهنده منفرد باشید، باید از پروتکلها استفاده کنید یا نه؟ در این حالت شما میدانید که دقیقاً چه کاری دارید انجام میدهید و در صورتی که پس از یک سال برای یک بهروزرسانی مجدداً کد خودتان مراجعه کنید هم نیاز به یادآوری نخواهید داشت. به خصوص این وضعیت در مواردی معنی بیشتری مییابد که مجبور باشید کد بیشتری بنویسید که از نظر اپلیکیشن یا تابعتان هیچ ضروری ندارد.
آیا پروتکل Fielder را که در بخشهای قبلی مطرح کردیم به خاطر دارید؟ چه میشود اگر به شما بگوییم که در آنجا لازم نبود الزامات پروتکل را بنویسید و لازم نبود آیکون قرمزرنگ کنار پروتکل را هر بار کلیک کنید تا همه متدها در همه وهلهها که پروتکل استفاده شده بود پیادهسازی شوند؟ آیا در این صورت عاشق پروتکل نمیشوید؟
واقعیت محض این است که در مثال فوق ما 6 مشخصه و 4 متد را هر بار نوشتهایم که شاید کاری دیوانهوار به نظر برسد، چون در این حالت 42 مشخصه و 28 متد داریم و منطق استفاده شده درون هر متد را نیز حساب نکردهایم. اکستنشنها به همراه پروتکلها قدرت بسیاری مییابند.
اینک به توضیح عملی گفتههای خود میپردازیم. در کد زیر بخشهایی از کدهای فوق را کپی کرده و چسباندهایم. تا مجدداً به کدهای فوق مراجعه نکنید:
در این بخش قصد نداریم در مورد روش نوشتن پروتکل توضیح دهیم؛ بلکه در مورد اکستنشن پروتکل صحبت خواهیم کرد. این اکستنشن به تعریف مقادیر پیشفرض برای همه چیزهایی که از پروتکل Fielder استفاده میکنند میپردازد. این بدان معنی است که از آنجا که متد یک متغیر دارد، لازم نیست پیادهسازی آن را در هر وهله از شیء که یک Fielder است پیادهسازی کنیم. ما تنها باید آن را یک بار ایمپورت کنیم و این کار را کردهایم. این به آن معنی است که اگر قصد داشتیم یک موقعیت دیگر به نام Catcher بسازیم، تنها کاری که باید انجام دهیم این است که آن را از Fielder دریافت کنیم. بدین ترتیب همه مقادیر مشخصه و متدهای پیشفرض درفت میشوند. ما تنها باید مشخصههای غیر آپشنال را که در اکستنشن پروتکل تعیین نشدهاند پیادهسازی کنیم.
بنابراین اگر در مورد مثال JSON که قبلاً ارائه کردیم تأمل کنید، میتوانید اکستنشنهایی برای هر کدام از پروتکلهایی که JSONManager یا DataManager استفاده میکنند بسازید و آنها را در کلاسهای جدید بگنجانید و نیازی به نوشتن کد اضافی به جز کد مورد نیاز برای مدیریت خاص آن کلاس یا سازه نیز وجود ندارد.
زیرنویس (Subscript)
منظور از یک زیرنویس تنها یک روش متفاوت برای نامگذاری اندیس است. در واقع کار کردن با زیرنویس به اندازه کار با آرایهها و دیکشنریها که در بخشهای قبلی بیان کردیم آسان است. شما میتوانید آرایهها را با استفاده از [myArray[0 برای دریافت عنصر نخست آرایه، زیرنویس کنید.
اگر نمیدانید باید بگوییم که آرایهها از اندیس صفر آغاز میشوند، چون در زمانهای قدیم آرایهها از نوع ارجاعی بودند و عدد 0 برای تعیین میزان فاصله گرفتن از نقطه شروع و تعیین موقعیت اندیسهای بعدی مورد نیاز بود.
دیکشنریها از زیرنویس با استفاده از کلیدهایشان استفاده میکنند. به عنوان مثال کد زیر:
میتواند برای اشاره به مقدار “Gaurdians of the Galaxy Part 1” استفاده شود. ما از زیرنویس روی کلید برای دریافت مقدار آن استفاده کردهایم. بنابراین چنان که گفتیم هیچ چیز جدیدی به جز یک نام تازه وجود ندارد.
زیرنویسها زمانی که در ماتریس استفاده میشوند به مفهومی جذاب تبدیل میشوند. به مثال زیر توجه کنید:
بحث ارجاع دهی این ماتریس چگونه خواهد بود؟ ساختار آن به صورت زیر است:
اگر بخواهیم به همه مقادیر دسترسی داشته باشیم، میتوانیم تعداد حلقهها را دو برابر کنیم:
بنابراین، استفاده از زیرنویس روشی کاملاً سرراست است و کافی است از اندیس هر مقداری که میخواهیم در ماتریس پیدا کنیم استفاده کنیم. در واقع بهتر است آنها را به صورت [[[value]]] در نظر بگیریم و سپس از بیرون به سمت داخل حرکت کنیم تا مقداری را که به دنبالش هستیم بیابیم.
متأسفانه نکته یا ترفند خاصی برای کار کردن با ماتریسها وجود ندارد و این کار صرفاً به تمرین و تجربه زیاد نیاز دارد.
سخن پایانی
در این نوشته با مطالب مفید زیادی آشنا شدیم. با استفاده از تعریف پروتکلها به افزایش تعریفپذیری و به لطف اکستنشنها به بهبود خوانایی کد کمک کردیم و با بهرهگیری از هردوی آنها قابلیت استفاده مجدد کد ارتقا مییابد. سپس چند مثال از زیرنویسها ارائه کردیم که منتهی به معرفی ماتریسها شد.
بنابراین در کدهای خود ابتدا از پروتکلها آغاز کنید و سپس از اکستنشنها به همراه پروتکلها استفاده کنید تا مقدار کدی که باید بنویسید را کاهش دهید. از اکستنشنها برای کمک به افراز کلاسهای بزرگ به دو فایل میتوان استفاده کرد. اگر همه مطالبی که در این نوشته خواندید را فراموش کردید، دستکم مطالبی که در این پاراگراف بیان کردیم را به خاطر داشته باشید.
بسیار خوشحال بودیم که در بخش بعدی این سلسله مطالب آموزش سوئیفت به معرفی «بستارها» (closures) بپردازیم؛ اما با توجه به این که این مفهوم جز در موقعیتهای پیچیده مورد نیاز نیست، صرفاً به معرفی روشهای افزایش خوانایی کد و تسهیل ادراک کد میپردازیم و تنها از کدهای دنیای واقعی استفاده میکنیم که گرچه ممکن است بهینه نباشند؛ اما قطعاً کامپایل میشوند و کارهایی را انجام میدهند. همچنین برخی مفاهیمی را بررسی خواهیم کرد که اینک آمادگی یادگیریشان را یافتهاید. بنابراین موضوعتی که در بخش دهم این سلسله آموزشها ارائه شده شامل ساختار کد، خوانایی و معرفی برخی مفاهیم است.
پس از این بخش از آموزشها دیگر تأکیدی نداریم که به تمرین بپردازید، چون شما قطعاً تاکنون خودتان متوجه شدهاید که تمرین کردن کدنویسی تا چه حد برای بهبود مهارتهای برنامهنویسی ضروری است. در واقع همه وقتهای خود را باید صرف تمرین کردن بکنید و زمانی که از تمرین کردن خسته شدید از راهنماهایی که در این زمینه وجود دارند بهره بگیرید.
اما باید بدانید که راهنماهایی که در زمینه پیادهسازی مفاهیم ارائه میشوند، به مسیری طولانی نیاز دارند و با ذهنیت آموزشی فاصله زیادی دارند. مطالعه این راهنماها باعث میشوند شما ذهنیت یک توسعهدهنده را بیابید یعنی متوجه شوید که شما هرگز به آن اندازه که میخواهید خوب نخواهید بود.
درک این نکته در همین مراحل ابتدایی بسیار ضروری است، به خصوص چون شما اینک آمادگی ساخت اپلیکیشنها برای خودتان یا هر فرد دیگر را یافتهاید، درک این ذهنیت موجب میشود که همواره انگیزه یادگیری موارد بیشتر را در خود حفظ کنید.
برای مطالعه قسمت بعدی این مجموعه مطلب آموزشی روی لینک زیر کلیک کنید:
اگر این نوشته برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامه نویسی Swift (سوئیفت) برای برنامه نویسی iOS
- مجموعه آموزشهای مهندسی نرمافزار
- آموزش برنامه نویسی سوئیفت (Swift): اشاره گرها و انواع داده — بخش دوم
- پوش نوتیفیکیشن (Push Notification) در iOS با استفاده از Swift — به زبان ساده
- آرایه ها در زبان برنامه نویسی سوئیفت (Swift) — به زبان ساده
==