رتبه موضوع:
  • 0 رای - 0 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
آموزش برنامه‌نویسی Swift برای iOS 10 با Xcode 8
#1
توی این تاپیک قصد دارم بطور خیلی ساده و خودمونی، برنامه‌نویسی برای iOS رو با زبان Swift نسخه‌ی 3 و در محیط Xcode نسخه 8 آموزش بدم. امیدوارم که مثل سایر آموزش‌ها مورد استقبال شما عزیزان قرار بگیره. برای جلوگیری از بی‌نظمی و شلوغ‌شدن تاپیک، این تاپیک بسته شده و بنابراین، اگه سؤالی درمورد هرکدوم از مباحث داشتین، لطفاً تاپیک جداگانه ایجاد کنین. ضمناً اگه در ابتدای کار، بنظرتون یکم مطالب پیچیده است یا سریع از هر موضوع رد میشیم، نگران نباشین. اول کار یه دور خیلی سریع توی دنیای سویفت میزنیم و بعد تمام این موارد و کار با محیط Xcode و... رو جزء به جزء آموزش میدیم. ضمناً اگه میخواین در حین خوندن این کدها و یادگیری Swift، بدون نیاز به سیستم مک و اجرای Xcode و توی مرورگر کدها رو تست و اجرا کنین، میتونین از سایت iSwift کمک بگیرین.

پس با ما همراه باشید تا قدم به قدم این سرفصلها رو آموزش بدیم (برای سهولت بیشتر در دسترسی و ترسیم روال کاری آموزش، فهرست مطالب رو توی همین پست اول میگذارم (این فهرست درحال تکمیل و بروزرسانیه. پس مرتب به این تاپیک سر بزنید) :

تشکر شده توسط: meysam1366 , amir
#2
معماری Swift
خیلی خوبه که در شروع کار، یه دیدگاه کلی درمورد ساختار زبان Swift و اینکه یه برنامه‌ی iOS نوشته‌شده با سویفت چطور بنظر میرسه داشته باشین. این جلسه به معماری کلی و ساختار طبیعی زبان Swift اختصاص داره و در جلسات آینده، جزئیات بیشتری رو مطرح میکنیم.

زیربنا
یه دستور کامل در سویفت، یک «عبارت» نامیده میشه. یه فایل Swift شامل چندین «خط» متن هست. شکستن خطوط برای سویفت معنا داره. ساختار عمومی و رایج در یه برنامه، بصورت «یک دستور در هر خط» تعریف شده:
print("hello")
print("world")

نقل قول:دستور print باعث چاپ خروجی توی پنجره‌ی Console در برنامه‌ی Xcode میشه.

میتونین بیش از یک عبارت رو توی یه خط ترکیب کنین، ولی لازمه که بینشون یه سمی‌کالن ; بگذارین:
print("hello"); print("world")

شما این اختیار رو دارین که در انتهای آخرین دستور یا دستوری که به‌تنهایی توی یک خط اومده، سمی‌کالن بگذارین یا نه، ولی هیچکس اینکار رو نمیکنه (بجز اونایی که بخاطر اجبار در C یا Objective-C بهش عادت کردن) :
print("hello");
print("world");

درمقابل، یه عبارت میتونه برای خوانایی بیشتر و جلوگیری از تبدیل‌شدن دستورات به خطوط طولانی، به چند سطر شکسته بشه. البته لازمه که این‌کار رو در مکان‌های بامعنایی انجام بدین تا Swift رو گیج نکنین. برای مثال، بعد از پرانتز باز میتونه یه مکان خوب برای شکستن سطر باشه:
print(
   "world")

توضیحات در برنامه، شامل هر چیزی میشن که بعد از دو اسلش توی یه خط بیان (مثل توضیحات C++) :
print("world") // this is a comment, so Swift ignores it

همچنین میتونین مثل زبان C، توضیحات رو بین /* ... */ قرار بدین و اینطوری، بخشی از یک خط یا چند خط رو تبدیل به توضیح کنید. برخلاف زبان C، توضیحات این‌شکلی رو میتونین بصورت تودرتو هم تعریف کنین.

توضیحات در برنامه، دو کاربرد خیلی مهم دارن:
  • ارائه‌ی توضیحاتی درمورد کدی که دارین مینویسین (برای مراجعات بعدی و ویرایش و...)
  • غیرفعال‌کردن بخشی از کد که از اجراش مطمئن هستین، برای اجرا و کامپایل سریع‌تر سایر بخش‌های برنامه (بدون نیاز به پاک‌کردنش)

بسیاری از ساختارها در سویفت از آکولادها برای تعریف شروع و پایانشون استفاده میکنن. برای مثال:
class Dog {
   func bark() {
       print("woof")
   }
}

بعنوان یه قرارداد نوشته‌نشده، بعد از آکولادها همیشه خطوط برنامه شکسته میشن و ادامه‌ی برنامه از خط بعد نوشته میشه که هدف از این کار، افزایش خوانایی هست (درست مثل کد بالا). Xcode خودش هم از این قرارداد پیروی میکنه، ولی واقعیت اینه که Swift اهمیتی به این موضوع نمیده و ساختارهایی مثل این کد هم مجاز هستن (و حتی در برخی مواقع راحت‌ترن) :
class Dog { func bark() { print("woof") }}

Swift یه زبان کامپایل‌شده است. معنای این حرف اینه که کد شما باید تولید بشه (build). یعنی باید از فیلتر یک کامپایلر بگذره تا از یه فایل متنی به یه شکل سطح‌پایین‌تر که کامپیوتر اون رو درک میکنه تبدیل بشه. بعد از این کار هست که برنامه‌ی شما میتونه اجرا بشه و واقعاً کارهایی که بهش گفتین رو انجام بده.

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

البته در موارد زیادی هم کامپایلر به شما اجازه میده برنامه رو با وجود اینکه یکسری «هشدار» درمورد کدتون وجود داره، تولید و اجرا کنین ولی همیشه عادت کنین که هشدارها رو جدی بگیرین و هرچیزی که به شما درموردشون گفته میشه رو اصلاح کنین.

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

نقل قول:خطاها و هشدارهای کامپایلر Swift میتونن واضح و آشکار یا مبهم و کلی باشن (که شما رو به سرنخی درمورد خطا هدایت نمیکنن). اغلب اوقات متوجه میشین که یه چیزی اشتباهه توی یه خط از برنامه، ولی کامپایلر سویفت به‌وضوح نمیگه که چی اشتباهه یا حتی کجا توی اون خط، اشتباه وجود داره تا توجهتون بهش جلب بشه. پیشنهاد من اینه که در چنین شرایطی، اون خط رو به چند خط کوچکتر با کدهای ساده‌تر بشکنین تا به نقطه‌ای برسین که بتونین حدس بزنین مشکل کجاست. سعی کنین عاشق کامپایلر باشین، حتی اگه پیام‌های خطایی که به شما میده کمک زیادی بهتون نمی‌کنه. فراموش نکنید که کامپایلر بهرحال بیشتر از شما میدونه، هرچند گاهی‌اوقات ممکنه دانشش رو به رخ شما نکشه.
تشکر شده توسط: arash691 , mamad_za , meysam1366 , amir
#3
هر چیزی یه شئ محسوب میشه
توی سویفت، «هر چیزی یه شیئه». خوب این حرف توی زبانهای برنامه‌نویسی مدرن و شئ‌گرای امروزی یه شعار کلیه ولی معناش چیه؟ خوب، این بستگی داره به اینکه منظورمون از «شئ» و «هر چیزی» چیه؟

اجازه بدین اینطوری بگیم که یه شئ، در حالت کلی، چیزیه که شما میتونین بهش یه پیام بفرستین. یه پیام هم درحالت کلی یه دستوره که باید اجرا کنه. برای مثال، میتونین به یه دانشجو دستور بدین: «درس بخون!» «معدلتو بگو!». در این حالت، این دستورات همون پیام‌ها هستن و دانشجو هم شیئی هست که شما اون پیام‌ها رو براش میفرستین.

توی Swift، دستور زبان ارسال پیام کارکتر نقطه . هست. ما با شئ شروع میکنیم و بعد یه نقطه میگذاریم و بعدش پیام رو مینویسیم (برخی از پیام‌ها بعدشون پرانتز دارن، ولی فعلاً این موضوع رو نادیده بگیرین. الگوی کامل ارسال پیام، یکی‌از همون جزئیاتی هست که گفتیم در جلسات آینده درموردش حرف میزنیم). این دستورات، یه دستور زبان مجاز و معتبر توی Swift محسوب میشه:
ali.study()
mina.sayAverage()

اینکه «هر چیزی» یه شئ محسوب میشه، شامل انواع داده‌ی مقدماتی هم میشه و معناش اینه که برای اونها هم میتونیم پیام بفرستیم. برای مثال، عدد 1 رو درنظر بگیرین. این عدد درظاهر یه عدد معمولی بنظر میرسه نه چیز دیگه. قطعاً اگه با هر زبان برنامه‌نویسی دیگری کار کارده باشین، تعجب نمیکنین اگه بگیم میتونین دستوراتی مثل این توی سویفت داشته باشین:
let sum = 1 + 2

ولی قطعاً متعجب میشین اگه بفهمین میشه بعد از 1 یه نقطه گذاشت و بعد یه پیام نوشت. این‌کار توی Swift معنا داره و مجازه (فعلاً نگران اینکه معناش چیه نباشین) :
let s = 1.description

ولی میتونین از این هم جلوتر بریم. به اون دستور 1 + 2 ساده و بی‌گناه که قبلاً گفتیم برگردین. واقعیت اینه که این دستور یه ترفند برای مخفی‌کردن همون ساختار ارسال پیام برای اشیاء محسوب میشه. در اینجا 1 یه شئ هست و + هم درحقیقت یه پیام محسوب میشه (ولی یه پیام با دستور زبان خاص که بهش میگیم عملگر یا Operator). توی دستور زبان سویفت، هر «اسم» یه شئ هست و هر «فعل» یه پیام محسوب میشه.

شاید جدی‌ترین تست درمورد اینکه یه شئ توی Swift چیه، قابلیت تغییردادنش باشه. یه نوع شئ رو میشه توی سویفت توسعه داد. معنای این حرف اینه که میتونین پیام‌های دلخواه خودتون رو روی اون نوع تعریف کنین. برای مثال شما نمیتونین در حالت عادی پیام sayHello رو برای یه عدد بفرستین، ولی میتونین نوع عدد رو تغییر بدین تا بتونین اینکار رو انجام بدین:
extension Int {
    func sayHello() {
        print("Hello, I'm (self)")
    }
}
1.sayHello() // outputs: "Hello, I'm 1"

بنابراین، توی سویفت 1 یه شئ محسوب میشه. در برخی زبان‌ها مثل Objective-C اینطور نیست و 1 یک نوع اولیه‌ی داخلی یا اصطلاحاً Scalar (اسکالر) محسوب میشه. بنابراین یکی‌از تفاوت‌های اصلی بین Swift و Objective-C در نوع نگاهشون به «هر چیزی» بعنوان شئ یا نوع داده‌ی مقدماتیه. توی سویفت، نوع داده‌ی مقدماتی یا Scalar وجود نداره و تمام انواع داده‌ها درنهایت شئ هستن. این، معنای حرفی هست که در ابتدا گفتیم (هر چیزی یه شئ محسوب میشه).

سه طعم مختلف از انواع شئ
اگه با Objective-C یا یه زبان شئ‌گرای دیگه آشنایی داشته باشین، شاید از توضیح مستندات Swift درباره‌ی نوع شئ 1 (عدد یک) شگفت‌زده بشین. در اغلب زبان‌ها مثل Objective-C یه شئ یک کلاس یا درواقع نمونه‌ای از یه کلاس محصول میشه (بعداً درمورد اینکه نمونه چیه توضیح میدم). سویفت هم دارای کلاس هست، ولی عدد 1 در Swift، یه کلاس یا نمونه‌ای از یه کلاس نیست: نوع 1 که اسمش Int هست، درحقیقت یه struct محسوب میشه و 1 هم یک نمونه از یه struct هست. ضمناً سویفت یه نوع دیگه از چیزهایی که میتونین براشون پیام بفرستین هم داره که بهش میگن enum.

بنابراین Swift سه نوع شئ داره: کلاس‌ها (class)، ساختارها (struct) و انواع شمارشی (enum). من ترجیح میدم که به اینها بعنوان سه طعم مختلف از انواع شئ نام ببرم. اینکه دقیقاً با هم چه تفاوت‌هایی دارن، جزئیات زیادی داره که در آینده مفصلاً بهش می‌پردازیم ولی درنهایت همشون دقیقاً انواع مختلف شئ هستن و شباهتشون به همدیگه خیلی بیشتر از تفاوت‌هاشونه. فعلاً همینقدر توی ذهنتون داشته باشین که این سه طعم مختلف وجود دارن.

این حقیقت که یه struct یا enum توی Swift یه نوع شئ محسوب میشه، کسانی که قبلاً با Objective-C آشنایی داشتن رو متعجب میکنه چون توی Objective-C هم struct و enum رو داریم ولی اونجا، شئ محسوب نمیشن. structها در سویفت خیلی مهمتر و فراگیرتر از structهای Objective-C هستن. این تفاوت بین نحوه‌ی مشاهده‌ی structها بین Swift و Objective-C میتونه زمانی که با Cocoa کار میکنین اهمیت پیدا کنه (درمورد Cocoa هم در اینده بطور کامل توضیح میدیم).
تشکر شده توسط: meysam1366 , amir
#4
متغیرها
یه متغیر، یه «اسم» برای یه شئ هست. ازنظر فنی، متغیر به یه شئ «اشاره می‌کنه» و درنتیجه یک «ارجاع» به شئ محسوب میشه. ازنظر غیرفنی میتونین بهش بعنوان یه جعبه نگاه کنین که یه شئ داخلش قرار میگیره. شئ میتونه بعداً تغییر کنه یا با یه شئ دیگه جایگزین بشه ولی اون جعبه (اسم متغیر) سر جاشه و وابسته به محتوای داخلش نیست. شیئی که متغیر بهش اشاره میکنه، «مقدار» متغیر نامیده میشه.

توی سویفت، هیچ متغیری بصورت ضمنی به‌وجود نمیاد و همشون باید صراحتاً «تعریف بشن». اگه به یه اسم برای یه چیزی نیاز دارین، باید بگین که «دارم یه اسم تعریف میکنم«. اینکار با یکی از کلمات کلیدی let یا var انجام میشه. توی Swift معمولاً تعریف متغیر و مقداردهی اولیه‌اش همزمان انجام میشه؛ یعنی میتونین با کمک علامت تساوی، بلافاصله به متغیرتون یه مقدار بدین و این‌کار هم جزئی از تعریفش باشه. این دو دستور دارن دو متغیر تعریف (و مقداردهی) میکنن:
let one = 1
var two = 2

وقتی که یه اسم وجود داشته باشه، آزادین که ازش استفاده کنین. برای مثال، میتونیم مقدار two رو برابر با مقدار one قرار بدین:
let one = 1
var two = 2
two = one

خط آخر کد بالا داره از هر دو اسم one و two که توی دو خط قبل تعریف شدن استفاده میکنه: اسم one در سمت راست تساوی برای اشاره به مقدار داخل جعبه‌ی one (یعنی 1) بکار میره؛ ولی اسم two در سمت چپ تساوی برای «جایگزین‌کردن» محتوای داخل جعبه‌ی two مورد استفاده قرار میگیره. دستوری شبیه این دستور اصطلاحاً «انتساب» نامیده میشه و علامت تساوی هم «عملگر انتساب» نام داره. در اینجا علامت تساوی به‌معنای بررسی برابربودن نیست (برخلاف فرمول‌های جبری)؛ بلکه یه دستوره به‌معنای اینکه: «مقدار هر چیزی که سمت راستم میاد رو بگیر و از اون برای جایگزین‌کردن محتوای چیزی که سمت چپ من نوشته‌شده استفاده کن».

دو نوع تعریف متغیر (let و var) با هم فرق دارن و درحقیقت «مقدار متغیری که با let تعریف شده رو نمیشه جایگزین‌کرد». متغیری که با let تعریف میشه، «ثابته» و مقدارش یکبار تعیین میشه و دیگه قابل ویرایش نیست. بنابراین این کد حتی کامپایل هم نمیشه:
let one = 1
var two = 2
one = two // compile error

همیشه این امکان وجود داره که یه متغیر رو با var تعریف کنین تا انعطاف‌پذیری بیشتری در اختیار داشته باشین، ولی اگه میدونین که هرگز نمیخواین مقدار اولیه‌ی یه متغیر رو تغییر بدین، بهتره که از let استفاده کنین چون این‌:ار به Swift اجازه میده که کارآمدتر رفتار کنه (درواقع اینقدر کارآمدتر که کامپایلر سویفت هرجایی که متغیری با var تعریف کرده باشین و بعداً در برنامه تغییرش نداده باشین، توجه شما رو بهش جلب میکنه و پیشنهاد میده که با let جایگزین کنین).

متغیرها دارای «نوع» هم هستن. این نوع موقع تعریف متغیر تعیین میشه و دیگه نمیشه عوضش کرد. برای مثال این کد کامپایل نمیشه:
var two = 2
two = "hello" // compile error

وقتی two تعریف میشه و مقدار اولیه‌ی 2 بهش میدین، یه عدد میشه (دقیق‌تر بخوایم بگیم، یه Int) و باید همیشه همینطور بمونه. شما میتونین مقدارش رو با 1 جایگزین کنین چون اونم یه Int هست، ولی نمیتونین اون رو با "hello" جایگزین کنین چون یه رشته است (دقیق‌تر بگیم، یه String) و یه String با یه Int برابر نیست.

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

نقل قول:ازنظر قراردادی (غیر اجباری)، اسامی انواع مثل String یا Int (یا Student و...) با حرف بزرگ شروع میشن و اسامی متغیرها با حرف کوچک. «این قرارداد رو بهم نزنین». اگه اینکار رو انجام بدین، کدتون باز هم ممکنه کامپایل بشه و بدون مشکل هم اجرا بشه ولی شخصاً چندنفر رو به خونه‌تون میفرستم که در تاریکی شب، کاسه‌های زانوهاتون رو از جا در بیارن!
تشکر شده توسط: meysam1366 , amir
#5
توابع
کدهای قابل اجرا مثل ali.study() یا مثلاً one = two نمیتونن مستقیماً هرجایی توی برنامه‌ی شما نوشته بشن. بطور کلی این کدها باید داخل بدنه‌ی یک «تابع» قرار داشته باشن. یه تابع، مجموعه‌ای از کدهاست که میتونیم صداش بزنیم تا اجرا بشن. عموماً یه تابع یه اسم داره که بهش در زمان تعریف تابع اختصاص میدیم. دستور زبان تعریف تابع، یکی دیگه از جزئیاتی هست که بعداً درموردش بیشتر حرف میزنیم ولی فعلاً این مثال رو درنظر داشته باشین:
func go() {
    let one = 1
    var two = 2
    two = one
}

این کد، مجموعه‌ای از کارها که باید انجام بشن رو مشخص میکنه: تعریف one، تعریف two، قراردادن مقدار one داخل two؛ و بعد به این مجموعه، یه «اسم» یعنی go رو اختصاص میده. اما دقت کنید که این کد، اون مجموعه از دستورات رو «اجرا» نمیکنه! این مجموعه، زمانی اجرا میشه که یکنفر اون تابع رو «صدا بزنه». بنابراین، باید یه جایی اینطوری بگیم:
go()

این دستور باعث میشه که تابع go فراخوانی و درنتیجه اجرا بشه. اما مجدداً یادتون باشه که این دستور هم باز خودش یه کد اجراییه و نمیتونه خارج از یه تابع قرار بگیره. بنابراین باید اینطوری بنویسیم:
func doGo() {
    go()
}

ولی صبر کنین ببینم! این دیگه داره رو اعصاب میره. خوب الان اینم تعریف یه تابع دیگه است که برای اجراش باید یکی دیگه doGo رو صدا بزنه و اونم باز یه کد اجراییه که باید توی یه تابع دیگه باشه! بنظر میاد توی یه حلقه‌ی بی‌نهایت گیر افتادیم و هیچ کدی درنهایت قابل اجرا نیست. اگه تمام کدهای اجرایی باید توی یه تابع باشن، درنهایت چه‌کسی این توابع رو قراره صدا بزنه؟ انگیزه و محرک اولیه باید از یه جای دیگه خارج از برنامه‌ی شما ایجاد بشه.

در دنیای واقعی، خوشبختانه این مشکل پیش نمیاد. دقت‌کنید که هدف نهایی شما نوشتن یه برنامه‌ی iOS هست. بنابراین، برنامه‌ی شما باید روی یه دستگاه iOS (یا شبیه‌ساز) و توسط سیستم‌عامل و مجری برنامه‌ها فراخوانی و اجرا بشه که منتظر یکسری تابع خاص هست تا اونها رو صدا بزنه. بنابراین شما باید شروع به نوشتن یکسری توابع ویژه کنین که میدونین مجری برنامه‌ها خودش اونها رو صدا میزنه. اینطوری برنامه‌ی شما یه راهی برای اجرا پیدا میکنه و فضاهایی در اختیار شما میگذاره تا توابعتون رو در زمان‌های موردنیاز صدا بزنین (مثلاً در زمانی که برنامه اجرا میشه یا وقتی که کاربر، یه دکمه توی رابط کاربری برنامه‌ی شما رو لمس میکنه).

نقل قول:Swift یه قانون ویژه‌هم داره که میگه فایلی به‌نام main.swift میتونه استثنائاً کدهای اجرایی رو در سطح بالا (خارج از توابع) داشته باشه و این فایل، کدی هست که واقعاً موقع اجرای برنامه‌ی شما توسط مجری برنامه‌ها فراخوانی میشه. میتونین برنامه‌ی خودتون رو داخل فایل main.swift بنویسین ولی معمولاً به این کار نیاز نخواهید داشت.
تشکر شده توسط: meysam1366
#6
ساختار یک فایل Swift
یه برنامه‌ی سویفت میتونه شامل یک یا چند فایل باشه. توی Swift یه فایل به‌معنای یه واحد بامعناست و درنتیجه قوانین مشخصی درباره‌ی ساختار کد سویفتی که میتونین داخلش بنویسین وجود داره (فرض میکنم که توی فایل main.swift نیستیم). فقط یکسری موارد خاص میتونن در سطح بالای یه فایل Swift (خارج از توابع) وجود داشته باشن:
  • دستورات import (واردکردن) ماژول‌ها: یه ماژول، یه واحد سطح‌بالاتر نسبت به یه فایل محسوب میشه. درواقع یه ماژول میتونه از چند فایل تشکیل شده باشه و تمام این فایل‌ها همدیگه رو بطور خودکار شناسایی کنن و ببینن؛ اما یه ماژول نمیتونه یه ماژول دیگه رو بدون استفاده از دستور import ببینه. برای مثال، نحوه‌ی کار با Cocoa توی یه برنامه‌ی iOS همینطوره: اولین خط فایل شما حاوی دستور import UIKit خواهد بود.
  • تعریف متغیرها: یه متغیر که در سطح بالای یک فایل تعریف میشه، متغیر سراسری نام داره: تمام کدهای میتونن اون رو ببینن و بهش دسترسی داشته باشن، بدون اینکه لازم باشه دستوری به شئ خاصی بفرستن و این متغیرها تا زمانی که برنامه درحال اجراست، وجود دارن.
  • تعریف توابع: تابعی که در سطح بالای یه فایل معرفی میشه هم یه تابع سراسری نام داره و تمام کدها میتونن اونو ببینن و صداش بزنن و لازم نیست پیامی رو برای شئ خاصی بفرستن تا بهش دسترسی پیدا کنن.
  • تعریف انواع شئ: تعریف یه کلاس (class)، ساختار (struct) یا نوع شمارشی (enum).

برای مثال، کد زیر، یه فایل مجاز در سویفت هست که از یه دستور import (فقط برای نمایش نحوه‌ی انجام این‌کار)، تعریف یه متغیر، یه تابع، یه کلاس، یه ساختار و یه نوع شمارشی تشکیل شده:
import UIKit
var one = 1
func changeOne() {
}
class Student {
}
struct Person {
}
enum WeekDay {
}

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

دقت کردین که من «نگفتم» که کد اجرایی میتونه در سطح بالای یه فایل قرار بگیره؟ علتش اینه که واقعاً نمیتونه! «فقط بدنه‌ی یک تابع میتونه شامل کدهای اجرایی باشه». یه دستور مثل one = two یا print(name) یه دستور اجراییه و نمیتونه در سطح بالای فایل نوشته بشه، ولی توی مثال قبلی ما، func changeOne() تعریف یه تابع هست و درنتیجه کدهای اجرایی رو میتونیم داخل آکولادهاش بنویسیم:
var one = 1
func changeOne() {
    let two = 2
    one = two
}

کدهای اجرایی رو مستقیماً نمیشه داخل آکولادهای class Student نوشت چون اونجا هم باز سطح بالای تعریف یه کلاس محسوب میشه نه بدنه‌ی یه تابع. اما یه کلاس میتونه شامل تعریف تابع باشه و اون تابع میتونه شامل کد اجرایی باشه:
class Student {
    let name = "ali"
    func sayName() {
        print(name)
    }
}

برای جمع‌بندی، این مثال یک فایل معتبر Swift رو نشون میده که ازنظر ساختار نمایشی و ظاهری، ساختار کدنویسی ممکن رو نمایش میده (از ساختار عجیب تعریف متغیر name توی نوع شمارشی WeekDays صرف‌نظر کنین. متغیرهای سراسری enum یکسری قوانین دیگه دارن که بعداً درموردشون توضیح میدم) :
import UIKit

var one = 1

func changeOne() {
    let two = 2
    func sayTwo() {
        print(two)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
    one = two
}

class Student {
    var name = "ali"
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}

struct Person {
    var name = "mina"
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}

enum WeekDay {
    let days : String {
        return "Friday"
    }
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}

واضحه که میتونیم هرچقدر میخوایم، این ساختار رو بصورت عمقی ادامه بدیم: میتونیم یه کلاس داخل یه کلاس دیگه تعریف کنیم که باز داخلش یه کلاس دیگه داره و... ولی بهتره هیچوقت زیاده‌روی نکنین.
تشکر شده توسط: meysam1366
#7
محدوده و طول‌عمر
توی یه برنامه‌ی Swift هرچیزی یه محدوده (Scope) داره که به‌معنای قابلیت دیده‌شدنش توسط سایر قسمتهای برنامه است. هر چیزی (کلاس، تابع، ساختار، متغیر و...) داخل یه چیز دیگه تعریف میشه و یه ساختار سلسله‌مراتبی از چیزها ایجاد میکنه. طبق قانون، «هر چیزی میتونه سایر چیزهایی که توی سطح خودش یا سطوح بالاتر از خودش (که خودش داخلشون تعریف شده) رو ببینه». منظور از سطوح اینهاست:
  • یه ماژول، یه محدوده ایجاد میکنه
  • هر فایل، یه محدوده برای خودش تعریف میکنه
  • آکولادهای باز و بسته، یه محدوده میسازن

وقتی یه چیزی رو تعریف میکنیم، توی یه سطحی داخل ساختار سلسله‌مراتبی که گفتیم تعریف میشه. موقعیتش توی این ساختار (محدوده‌اش) مشخص میکنه که توسط سایر چیزها دیده میشه یا نه. مجدداً به آخرین کدی که توی پست قبلی گذاشتیم دقت کنین. توی تعریف کلاس Student یه متغیر به‌اسم name تعریف کردیم و یه تابع به‌اسم sayName هم ساختیم. کد «داخل» آکولادهای sayName میتونه چیزهای «خارج» از آکولاد رو که توی «سطح بالاتر» تعریف شدن ببینه و درنتیجه میتونه به متغیر name دسترسی داشته باشه. بطور مشابه کد داخل بدنه‌ی تابع changeOne میتونه متغیر one رو که توی سطح بالای فایل تعریف شده ببینه. درحقیقت «هرچیزی» توی این فایل میتونه متغیر one رو که در سطح بالای فایل تعریف شده، ببینه.

بنابراین همونطور که مشاهده میکنید، محدوده، روش بسیار مهمی در «اشتراک اطلاعات» محسوب میشه. دو تابع مختلف که داخل کلاس Student تعریف شدن، «هردوتا» میتونن متغیر name رو که توی کلاس تعریف شده ببینن. همچنین کد داخل ساختار Person و کد داخل نوع شمارشی WeekDay میتونن متغیر one که در سطح بالاتر تعریف شده رو ببینن.

علاوه‌بر محدوده، هر چیزی یه «طول عمر» هم داره که رابطه‌ی تنگاتنگی با محدوده‌اش داره. هر چیزی، تا وقتی زنده است و وجود داره که محدوده‌ای که اون رو در بر گرفته، وجود داشته باشه. بنابراین، توی کد قبلی، متغیر one تا وقتی وجود داره که خود فایل وجود داره (درواقع تا وقتی که برنامه اجرا میشه). این متغیر سراسری و «ماندگار» (Persistent) هست. اما متغیر name که داخل کلاس Student تعریف شده، فقط تا وقتی که یه نمونه از این کلاس وجود داشته باشه، زنده است و وجود خواهد داشت (یکم جلوتر توضیح میدم که یعنی چی).

چیزهایی که توی سطوح عمیق‌تر تعریف میشن، عمر کوتاه‌تری دارن. این کد رو ببینید:
func silly() {
    if (true) {
        class Cat {}
        var one = 1
        one = one + 1
    }
}

این کد همونطور که از اسمش مشخصه، احمقانه است ولی مجازه: یادتون باشه که گفتم تعریف متغیرها و توابع و انواع شئ میتونه توی هر آکولاد باز و بسته‌ای انجام بشه. توی کد بالا، کلاس Cat و متغیر one هیچوقت ایجاد نمیشن مگه اینکه یکنفر تابع silly رو صدا بزنه و حتی در این زمان هم اونها فقط در لحظه‌ی کوتاهی که جریان اجرای کد از داخل ساختار شرطی if میگذره، ایجاد میشن. با این توضیحات، فرض کنید که تابع silly فراخوانی شده. مسیر اجرای برنامه وارد ساختار if میشه. در اینجا کلاس Cat تعریف میشه و به‌وجود میاد. بعد متغیر one تعریف و تولید میشه و بعدش دستور اجرایی one = one + 1 اجرا میشه و بعدش این محدوده تمام میشه و هم کلاس Cat و هم متغیر one از بین میرن. درنتیجه کلاس Cat و متغیر one، توی طول‌عمر کوتاهشون کاملاً برای سایر قسمت‌های برنامه نامرئی هستن (الان میتونین متوجه بشین چرا؟).
تشکر شده توسط: meysam1366
#8
اعضای یک شئ
داخل سه نوع شئ (class و struct و enum)، چیزهایی که در سطح بالا تعریف میشن، اسامی خاصی دارن که علتش بیشتر بخاطر دلایل سنتی و قدیمیه. بذارین کلاس Student رو بعنوان یه مثال درنظر بگیریم:
class Student {
    var name = "ali"
    func sayName() {
        print(name)
    }
}

توی این کد:
  • name یه متغیره که توی سطح بالای تعریف یه شئ معرفی شده. بنابراین بهش یه خصوصیت (property) یا فیلد (field) از اون شئ میگیم.
  • sayName یه تابع هست که توسط سطح بالای تعریف یه شئ معرفی شده. بنابراین بهش یه رفتار (behavior) یا متد (method) از اون شئ میگیم.

چیزهایی که در سطح بالای یه شئ تعریف میشن (فیلدها، متدها و هر نوع شئ دیگه که توی اون سطح تعریف شده باشه، در مجموع اعضا (members) یا عناصر اون شئ رو تشکیل میدن. اعضا اهمیت خاصی دارن چون «پیام‌هایی» رو تعریف میکنن که شما میتونین برای اون شئ بفرستین.
تشکر شده توسط: meysam1366
#9
فضاهای نام
یه «فضای نام» یا namespace یه محدوده‌ی نام‌گذاری‌شده در برنامه است. یه فضای نام، این خاصیت رو داره که چیزهای داخلش توسط چیزهایی که بیرون از اون فضای نام قرار دارن، قابل دسترسی نیست؛ مگه اینکه اسم اون فضای نام رو اعلام کنن. این یه قابلیت خوبه چون اجازه میده از اسامی تکراری توی مکان‌های مختلف بدون تداخل در برنامه استفاده کنیم. واضحه که فضاهای نام و محدوده‌ها، مفهومی بسیار نزدیک به هم دارن.

فضاهای نام به شما کمک میکنن که اهمیت تعریف یه شئ در سطح بالای یه شئ دیگه رو درک کنین. مثل این کد:
class Student {
    class Score {}
}

این روش تعریف کلاس Score باعث میشه که این کلاس یه «نوع تودرتو» (Nested) بشه. با این‌کار، کلاس Score داخل Student مخفی میشه. در اینجا Student یه فضای نام هست! کد داخلی Student میتونه کلاس Score رو ببینه و مستقیماً صدا بزنه ولی کدهای خارج از Student نمیتونن و برای دسترسی بهش، باید صراحتاً اسم فضای نامش رو بیارن تا از مرز فضای نام رد بشن. برای این‌کار باید اول اسم فضای نام و بعد یه نقطه و بعد اسم کلاس مربوطه رو بنویسین. درواقع خارج از فضای نام Student، کلاس Score رو باید اینطوری صدا بزنیم:
Student.Score

فضای نام به‌تنهایی هیچ پنهان‌سازی یا خصوصی‌سازی خاصی رو انجام نمیده و فقط یه ابزار برای راحتی بیشتر در جداسازی اسامی مشابه برای جلوگیری از تداخل هست. برای مثال، این کد رو درنظر بگیرین:
class Student {
    class MyClass {}
}

enum WeekDay {
    class MyClass{}
}

در اینجا هم کلاس Student و هم نوع شمارشی WeekDay دارای کلاس MyClass هستن ولی این دو کلاس هم‌نام، با هم تداخل پیدا نمیکنن چون هرکدوم توی فضای نام خاص خودشون هستن و میتونیم درصورت نیاز به استفاده ازشون، با کمک فضای نامشون بصورت Student.MyClass و WeekDay.MyClass اونها رو از هم تفکیک کنیم.

دقت کنید که استفاده از کارکتر نقطه و بعد اسم کلاس داخل فضای نام، همون مفهوم فرستادن پیام با کمک نقطه است و مفهوم جدیدی نیست. در عمل، ارسال پیام به شما اجازه میده که داخل محدوده‌هایی که بدون این روش قادر به دیدنشون نبودین رو ببینین. کد داخل کلاس Student نمیتونه «بطور خودکار» کلاس MyClass که داخل WeekDay تعیرف شده رو ببینه ولی میتونه این‌کار رو با یک مرحله‌ی اضافه یعنی بصورت WeekDay.MyClass انجام بده. علت این موضوع اینه که کد داخل کلاس Student خود کلاس WeekDay رو میبینه (چون توی سطح بالاترش تعریف شده) و ازطریق ارسال پیام برای اون نوع شمارشی، میتونه داخلش رو ببینه و به کلاس MyClass که داخلش تعریف شده دسترسی پیدا کنه.
تشکر شده توسط: meysam1366
#10
ماژول‌ها
فضاهای نام سطح بالا ماژول هستن. بطور پیش‌فرض برنامه‌ی شما یه ماژوله و درنتیجه یه فضای نام محسوب میشه که اسمش همون اسم برنامه‌ی شماست. برای مثال، اگه اسم برنامه‌ی من MyApp باشه، اگه کلاسی به‌اسم Student در سطح بالای یه فایل داخل برنامه‌ام تعریف کنم، اسم «واقعی» کلاس MyApp.Student هست. اما معمولاً نیاز به استفاده از این اسم واقعی ندارم چون سایر کدهای من داخل همون فضای نام هستن و میتونن Student رو مستقیماً ببینن.

فریمورک‌ها هم ماژول هستن و درنتیجه فضای نام محسوب میشن. وقتی یه ماژول رو وارد برنامه‌ی خودتون میکنین (import)، تمام تعریف‌های سطح بالای اون ماژول توی کدتون قابل مشاهده میشن، بدون اینکه لازم باشه اسم فضای نام ماژول رو صراحتاً برای اشاره بهشون استفاده کنین.

برای مثال، فریمورک Foundation توی Cocoa رو درنظر بگیرین که کلاس NSString داخلش تعریف شده. این فریمورک یه ماژول محسوب میشه و وقتی که یه برنامه برای iOS مینویسین، توی کدتون چنین دستوری خواهید داشت:
import Foundation

یا مثلاً برای استفاده از UIKit چنین دستوری مینویسین:
import UIKit

در این حالت میتونین مستقیماً از NSString استفاده کنین و نیازی نیست حتماً بگین Foundation.NSString (البته اگه دوست داشتین میتونین اینکار رو انجام بدین، مثلاً اگه حواستون نبود و یه کلاس NSString دیگه توی ماژول خودتون تعریف کرده بودین، اونوقت «باید» برای فرق‌گذاشتن بینشون، اسم کامل Foundation.NSString رو بکار ببرین). همچنین میتونین فریمورکهای شخصی خودتون رو هم بسازین و اینها هم ماژول خواهند بود.

سویفت خودش توی یه ماژول تعریف شده (ماژول Swift). کد شما «همیشه بصورت مخفیانه و ضمنی، ماژول Swift رو وارد میکنه». میتونین این‌کار رو مستقیماً با نوشتن دستور import Swift در شروع فایل هم انجام بدین. هرچند نیازی به این کار وجود نداره ولی نوشتنش هم آسیبی ایجاد نمیکنه.

این واقعیت، اهمیت زیادی داره چون پرده از یک راز بزرگ برمیداره: دستوراتی مثل print از کجا میان و چطور میشه از اونها خارج از ساختار ارسال پیام برای یک شئ استفاده کرد؟ درحقیقت print یه تابع هست که در سطح بالای ماژول Swift تعریف شده و کد شما میتونه تمام تعاریف ایجادشده در سطح بالای این ماژول رو ببینه چون اون رو import میکنه. تابع print به‌همین‌خاطر توی تمام کدهای شما مثل هر تابع سطح بالای دیگه قابل دسترسی هست و سراسری محسوب میشه و کد شما برای صدازدنش نیاز به مشخص‌کردن فضای نامش نداره. شما «میتونین» فضای نامش رو هم ذکر کنین و کاملاً دستوری مثل کد زیر مجازه:
Swift.print("hello")

ولی احتمالاً هیچ‌وقت لازم نمیشه اینطوری بنویسین؛ چون دلیلی وجود نداره که توی سطح بالا، تابع print اختصاصی خودتون رو نمینویسه.

نقل قول:میتونین تمام تعریف‌هایی که توی سطح بالای Swift انجام شده رو ببینین و مطالعه کنین و این‌کار یکی‌از موارد مفیدی هست که بهتره انجام بدین. برای این‌کار، روی کلمه‌ی print توی کدتون، Command+Click کنین (دکمه‌ی Command رو از روی صفحه‌کلید نگه‌دارین و روی این کلمه توی محیط Xcode کلیک کنین). همچنین میتونین صراحتاً دستور import Swift رو بنویسین و روی کلمه‌ی Swift توی کدتون Command+Click کنین. با این‌کار، تمام تعاریف سطح بالای Swift رو خواهید دید! البته هیچ کد اجرایی خاصی رو از سویفت اونجا نمیبینین ولی خود تعاریف موجود برای تمام اصطلاحات و دستورات Swift رو مشاهده میکنین (مثل توابع سطح بالا نظیر print و عملگرهایی مثل + و انواع داخلی مثل Int و String (دنبال struct Int و struct String و... بگردین).
تشکر شده توسط: meysam1366
#11
نمونه‌ها
انواع شئ (کلاس، ساختار، نوع شمارشی) یه قابلیت مشترک و مهم دارن: میتونین از اونها شئ ایجاد کنین. در عمل، وقتی که یه نوع شئ تعریف میکنین، فقط دارین خود «نوع» رو معرفی میکنن. نمونه‌سازی یعنی اینکه از اون نوع یه چیزی (یه نمونه) بسازین.

برای مثال، من میتونم یه کلاس Dog تعریف کنم و به کلاسم یه اسم بدم:
class Student {
    func talk() {
        print("Hi")
    }
}

اما الان درحقیقت من هیچ دانش‌آموزشی توی برنامه‌ام ندارم. من فقط گفتم که «نوع» یه دانش‌آموز توی برنامه‌ی من چطوری میتونه باشه، اگه یکی ازش داشته باشم. برای اینکه یه دانش‌آموز واقعی داشته باشیم، باید یکی «بسازیم». فرایند ایجاد یه شئ واقعی «دانش‌آموز» که نوعش کلاس Student هست، اصطلاحاً نمونه‌سازی از کلاس Student نامیده میشه و نتیجه‌اش یه شئ هست (یه نمونه‌ی دانش‌آموز).

توی سویفت، نمونه‌ها رو میشه با کمک اسم نوع بعنوان اسم یک تابع و فراخوانی اون تابع ایجاد کرد. برای این‌کار باید از پرانتزها استفاده کنیم. وقتی که پرانتزها رو به اسم یه نوع شئ اضافه می‌کنین، دارین یه نوع خاص از پیام رو به اون نوع شئ میفرستین: «یه نمونه از خودت تحویلم بده»!

بنابراین، الان میخوام که یه دانش‌آموز ایجاد کنم:
let ali = Student()

خیلی چیزها توی این کد وجود داره! من دو کار انجام دادم: یه نمونه از Student ساختم که این‌کار بهم یه شئ نمونه از Student تحویل میده. بعد این نمونه رو توی یه متغیر به‌اسم ali ذخیره کردم (یعنی یه متغیر تعریف کردم به‌اسم ali و مقدار اولیه‌ی اون رو برابر با شئ جدیدی که از نوع Student تحویل گرفتم قرار دادم).

الآن ali یه نمونه از Student هست. ضمناً چون من از let استفاده کردم، ali همیشه به همین نمونه از Student اشاره میکنه. میتونستم از var هم استفاده کنم ولی حتی در اون حالت هم چون مقداردهی اولیه‌ی ali با یه شئ نمونه از Student انجام شده، همیشه باید ali به یه شئ نمونه از Student اشاره کنه و نمی‌تونیم اشیائی که از سایر انواع ایجاد میشن رو داخلش بگذاریم.

حالا که یه نمونه از Student داریم، میتونیم «پیام‌های نمونه‌ای» رو براش بفرستیم. فکر میکنین این پیام‌ها چی هستن؟ درسته: فیلدها و متدهای Student پیام‌های ما رو تشکیل میدن! برای مثال:
let ali = Student()
ali.talk()

این کد، هم مجاز و معتبره و هم کار میکنه: واقعاً باعث میشه که کلمه‌ی Hi توی کنسول (رابط خط‌فرمان Xcode) ظاهر بشه. من یه دانش‌آموز ساختم و ازش خواستم حرف بزنه!

یه درس مهم اینجا وجود داره، پس اجازه بدین یکم مکث کنیم تا درموردش توضیح بدم. بطور پیشفرض، فیلدها و متدها «نمونه‌ای» هستن. بنابراین نمیشه از اونها بعنوان پیام برای خود نوع شئ استفاده کنین. معنای این حرفم اینه که «باید» یه نمونه وجود داشته باشه و پیام رو برای اون نمونه بفرستین. با این توصیف، چنین دستوری اشتباهه و کامپایل نمیشه:
Student.talk() // compile error

این امکان وجود داره که تابع talk رو به‌نحوی تعریف کنیم تا دستور Student.talk() مجاز باشه، ولی اونوقت اون میشه یه نوع متفاوت از توابع که بهش میگیم متدهای کلاسی یا static و باید این رو موقع تعریف مشخص کنیم.

همین موضوع درمورد فیلدها هم وجود داره. برای اینکه متوجه بشین، بذارین یه فیلد name به Student اضافه کنیم:
class Student {
    var name = ""
    func talk() {
        print(name)
    }
}

الان هر دانش‌آموز یه اسم هم داره ولی این اسم متعلق به خود نوع دانش‌آموز نیست و این نمونه‌هاش هستن که هرکدوم برای خودشون name دارن:
let st = Student()
st.name = "ali"
st.talk() // output: ali

میتونیم فیلدهایی مثل name رو هم طوری تعریف کنیم که دستوری مثل Student.name مجاز باشه ولی به این فیلدها هم میگن فیلدهای کلاسی یا static و موقع تعریف، باید این موضوع رو مشخص کنیم.
تشکر شده توسط: meysam1366
#12
چرا از نمونه‌ها استفاده کنیم؟
حتی اگه هیچ چیزی مثل نمونه‌ها وجود نداشته باشه، خود نوع شئ هم یه شئ محسوب میشه. ما اینو از اینجا میفهمیم که میتونیم برای نوع شئ هم پیام بفرستیم: میشه با نوع شئ بعنوان فضای نام برخورد کرد و مستقیماً واردش شد (مثل Student.MyClass که قبلاً درموردش حرف زدیم). بعلاوه از اونجا که اعضای کلاسی یا static وجود دارن، میتونیم یه متد رو مستقیماً روی یه کلاس، ساختار یا نوع شمارشی فراخوانی کنیم و به فیلدهای static اونها هم اشاره کنیم. با این اوصاف، اصلاً چرا اشیاء نمونه وجود دارن و باید ازشون استفاده کنیم؟

جواب این سؤال تا حد زیادی به ماهیت فیلدهای نمونه‌ای مربوط میشه. مقدار هر فیلد نمونه‌ای براساس همون «نمونه‌ی خاص» تعریف میشه. اینجا دقیقاً همون جایی هست که نمونه‌ها کاربرد واقعی و قدرتشون رو پیدا میکنن.

دوباره کلاس Student رو درنظر بگیرین. من بهش یه فیلد name اضافه کردم و متد talk هم ازش استفاده میکرد. یادتون باشه که این متد و فیلد، نمونه‌ای بودن. وقتی یه شئ جدید از کلاس Student میسازیم، اسمش (فیلد name) خالیه ولی بصورت var تعریف شده و بنابراین، وقتی یه نمونه دانش‌آموز داشته باشیم، میتونیم مقدار این فیلد رو تغییر بدیم:
let st = Student()
st.name = "ali"

همچنین میتونیم این مقدار رو نمایش بدیم:
print(st.name) // output: ali

نکته‌ی مهم در اینجا اینه که ما میتونیم بیش از یه نمونه‌ی دانش‌آموز داشته باشیم و دو نمونه‌ی مختلف میتونن دو اسم متفاوت داشته باشن:
let st1 = Student()
let st2 = Student()
st1.name = "ali"
st2.name = "mina"
st1.talk() // output: ali
st2.talk() // output: mina

دقت‌کنید که فیلد name هیچ کاری به اسم متغیری که خود شئ Student رو داخلش ذخیره کردیم (st1 و st2 و...) نداره. این متغیر فقط یه جعبه برای نگهداری شئ نمونه است. میتونیم یه شئ رو از یک ظرف توی یه ظرف دیگه بگذاریم ولی خود شئ محتوای داخلی خودش رو نگهداری میکنه:
let st1 = Student()
var st2 = Student()
st1.name = "ali"
st2.name = "mina"
print(st1.name) // output: ali
print(st2.name) // output: mina
st2 = st1
print(st2.name) // output: ali

توی این کد اگه به‌دقت نگاه کنین، اسم st2 رو تغییر ندادیم، بلکه محتوای جعبه‌ی st2 رو با محتوای st1 جایگزین کردیم. بنابراین فیلد name اون چیزی که الان توی st2 قرار داره، مقدار ali داره.

قدرت کامل برنامه‌نویسی شئ‌گرا الان مشخص شد. یه نوع شئ Student وجود داره که مشخص میکنه «یه دانش‌آموز چیه»! تعریف ما از Student مشخص میکنه که یه نمونه از این نوع (هر دانش‌آموز) یه خصوصیت به‌اسم name داره و یه متد به‌اسم talk ولی هر نمونه، مقدار خاص خودش رو برای خصوصیت name داره. این نمونه‌ها با هم فرق میکنن و هرکدوم «وضعیت» داخلی خاص خودشون رو دارن. بنابراین نمونه‌های مختلفی از یه نوع، مثل هم «رفتار میکنن» (هم st1 و هم st2 میتونن حرف بزنن و وقتی پیغام talk رو براشون میفرستیم، این‌کار رو انجام میدن)، اما نمونه‌های متفاوتی هستن و میتونن خصوصیات متفاوتی داشته باشن: اسم st1 مقدار ali و اسم st2 مقدار mina داره.

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

یه شئ نمونه، نه‌تنها مسئول مقادیر داخلی خودشه، بلکه مسئولیت «طول عمر» خصوصیاتش هم هست. فرض کنید که یه شئ نمونه از نوع Student ایجاد میکنیم و خصوصیت name اون رو با مقدار ali تنظیم میکنیم. بعد این شئ مقدار رشته‌ی ali رو تا زمانی که ما فیلد name رو با مقدار جدیدی جایگزین نکنیم، برامون نگه میداره و این‌کار تا زمانی که «خود شئ نمونه وجود داره»، ادامه پیدا میکنه.

بطور خلاصه، یه نمونه هم شامل کد میشه و هم شامل داده‌ها. کدش رو از نوع خودش میگیره و این کد بین تمام نمونه‌های اون نوع خاص، مشترکه. اما داده‌های هر شئ فقط به خودش مربوطه. داده‌ها میتونن تا زمانی که یه نمونه وجود داشته باشه، باقی بمونن. درواقع هر شئ در هر لحظه یه «وضعیت» داره که مجموعه‌ای از خصوصیات شخصی خودشه. یه نمونه، دستگاهی برای نگهداری وضعیت محسوب میشه. درحقیقت یه جعبه برای نگهداری داده‌هاست.
تشکر شده توسط: meysam1366
#13
self
یه نمونه، یه شئ هست و یه شئ گیرنده‌ی پیغام‌هاست. بنابراین یه شئ نیاز به راهی برای ارسال پیام برای خودش داره. این‌کار با کمک یه کلمه‌ی جادویی به‌اسم self انجام میشه. این کلمه میتونه هرزمان که یه نمونه نیاز به دسترسی به خودش داشته باشه، به‌کار بره.

برای مثال، بذارین یکبار دیگه کلاس Student رو با کمک این کلمه‌ی کلیدی تعریف کنیم:
class Student {
    var name = ""
    var message = "Hello"
    func talk() {
        print(self.message)
    }
}

به همین روش میتونم یه متد نمونه‌ای دیگه به‌اسم speak تعریف کنم که یه اسم مستعار برای talk محسوب بشه. درواقع پیاده‌سازی متد speak فقط خیلی ساده متد talk از همون شئ رو فراخوانی میکنه:
class Student {
    var name = ""
    var message = "Hello"
    func talk() {
        print(self.message)
    }
    func speak() {
        self.talk()
    }
}

فراموش نکنید که کلمه‌ی self فقط توی متدهای نمونه‌ای قابل استفاده است. وقتی که کد یه نمونه اجرا میشه، self به همون نمونه اشاره میکنه. اگه عبارت self.name توی کد یه متد نمونه از کلاس Student استفاده بشه، به‌معنای name «این» شئ نمونه از نوع Student هست که کدش در اون لحظه داره اجرا میشه.

خوب بنظر میاد که هرگونه استفاده از self کاملاً اختیاری باشه. برای مثال این کد هم بدون مشکل کار میکنه:
class Student {
    var name = ""
    var message = "Hello"
    func talk() {
        print(message)
    }
    func speak() {
        talk()
    }
}

علت این موضوع اینه که اگه گیرنده‌ی پیام رو مشخص نکنید، و پیام «قابل ارسال برای شئ جاری (یعنی همون self) باشه»، کامپایلر خودش self رو بعنوان گیرنده‌ی پیام معریف میکنه. اینکه میگم قابل ارسال باشه معناش اینه که شئ جاری، متد یا فیلد یا نوع داخلی و... به اون نام که شما استفاده کردین رو داشته باشه. هرچند من شخصاً هیچوقت اینکار رو انجام نمیدم (مگه اینکه تصادفاً از دستم در بره). بعنوان یه روش کدنویسی، ترجیح میدم که صراحتاً self رو بکار ببرم. شخصاً خوندن و درک کدی که از self استفاده نکرده باشه رو سخت‌تر میدونم و ضمناً مواردی هم وجود داره که شما «باید» self رو قید کنید. بنابراین ترجیح میدم هرجایی که مجاز به استفاده از self باشم، این‌کار رو انجام بدم.
تشکر شده توسط: meysam1366
#14
حریم خصوصی
قبلاً گفتم که یه فضای نام، به‌تنهایی بعنوان یک ظرف قابل مدیریت برای کنترل دسترسی به اسامی داخلش عمل نمیکنه. البته گاهی‌اوقات چنین مرزی موردنیاز خواهد بود. برای مثال، ممکنه نخوایم تمام داده‌های ذخیره‌شده در داخل یه نمونه توسط سایر نمونه‌ها و بخش‌های برنامه که خارج از خود شئ قرار دارن، قابل ویرایش یا حتی مشاهده باشن. یا حتی ممکنه دوست نداشته باشیم تمام متدهای داخل یه نمونه، توسط نمونه‌های دیگه فراخوانی بشن. هر زبان شئ‌گرای باحیایی نیاز به یه راه برای محافظت از این عناصر درقالب یک حریم خصوصی داره (روشی که ازطریق اون، سایر اشیاء نتونن به‌راحتی عناصری رو که دوست نداریم دیده بشن، ببینن). برای مثال:
class Student {
    var name = ""
    var message = "Hello"
    func talk() {
        print(self.name)
    }
    func sayHello() {
        print(self.message)
    }
}

در اینجا، اشیاء دیگه میتونن فیلد message شئ نمونه‌ای که از این کلاس ایجاد میشه رو تغییر بدن. از اونجا که این خصوصیت توسط متد speak استفاده شده، ممکنه با شرایطی مواجه بشیم که وقتی sayHello رو صدا بزنیم، پیغام Goodbye چاپ بشه! قطعاً این موضوع چیزی نیست که مدنظر ما باشه:
let st = Student()
st.message = "Goodbye"
st.sayHello // output: Goodbye

شاید با خودتون بگین: چه کاریه آخه! چرا متغیر message رو با let تعریف نمیکنی؟ اونو ثابتش کن! اینطوری دیگه کسی نمیتونه تغییرش بده.

خوب این جواب خوبیه ولی نه به‌حد کافی. دو مشکل در این روش وجود داره. فرض کنین میخوام خود شئ نمونه‌ی Student بتونه مقدار self.message رو تغییر بده. در اینصورت باید var باشه وگرنه خودشم نمیتونه مقدارش رو عوض کنه. همچنین فرض کنین که اصلاً نمیخوام اشیاء دیگه بفهمن که پیغامی که این شئ نمونه چاپ میکنه چیه (مگه اینکه متد sayHello رو صدا بزنن). حتی اگه با let هم تعریفش کنیم، بازم بقیه‌ی اشیاء میتونن مقدار این فیلد رو «بخونن» و ممکنه من نخوام چنین اتفاقی بیفته.

برای حل این مسئله، Swift کلمه‌ی کلیدی private رو معرفی کرده. بعداً بطور کامل درمورد تمام تأثیرات جانبی این کلمه‌ی کلیدی توضیح میدم، ولی فعلاً همینقدر کافیه که بدونین مشکلمون رو حل میکنه:
class Student {
    var name = ""
    private var message = "Hello"
    func talk() {
        print(self.name)
    }
    func sayHello() {
        print(self.message)
    }
}

نقل قول:کلمات رزرو شده
اصطلاحات خاصی مثل class و func و var و let و if و private و import و خیلی چیزهای دیگه که کم‌کم درطی این آموزش باهاشون آشنا میشین، توی سویفت «رزرو شده» هستن (جزئی از خود زبان محسوب میشن). معنای این حرف اینه که شما نمیتونن از اونها بعنوان شناسه (اسم یه کلاس، تابع، متغیر و...) استفاده کنین. اگه چنین کاری انجام بدین، یه خطای کامپایل میگیرین که کلمه‌ی identifier داخلش نوشته شده.

برای اینکه به‌زور از یه کلمه‌ی رزرو شده (کلمه‌ی کلیدی) بعنوان شناسه استفاده کنین، اون رو بین دو کارکتر Backtick قرار بدین. برای مثال، این کد عجیب و غریب، کاملاً مجاز و قانونیه:
class `func` {
    func `if`() {
        let `class` = 1
    }
}

خوب الان name یه خصوصیت عمومیه ولی message خصوصیه و توسط نمونه‌های اشیاء دیگه که خارج از کلاس Student قرار دارن، دیده نمیشه. یه نمونه‌ی Student میتونه از self.message استفاده کنه ولی یه شئ از کلاس Teacher که یه ارجاع به یه شئ از نوع Student به‌اسم ali داره، نمیتونه بگه ali.message چون خصوصیه و از بیرون، قابل مشاهده نیست.

درس مهمی که اینجا وجود داره اینه که عناصر اشیاء بطور پیشفرض «عمومی» هستن و اگه نیاز به حریم خصوصی دارین، باید درخواستش کنین. تعریف کلاس یه فضای نام ایجاد میکنه که سایر اشیاء رو وادار میکنه از یه ساختار نقطه‌گذاری اضافه برای دسترسی به محتوای داخلی اون استفاده کنن، ولی بهرحال تمام اشیاء دیگه همچنان «میتونن» به محتوای داخلی فضای نام اشاره کنن و فضای نام، به خودی خود، هیچ دری برای چهارچوبش تعریف نمیکنه و همه‌چیز از بیرون قابل مشاهده است. کلمه‌ی کلیدی private به شما اجازه میده که درها رو ببندین.
تشکر شده توسط: meysam1366
#15
طراحی ساختار برنامه
چه انواع شیئی توی برنامه نیاز داریم؟ چه متدها و خصوصیاتی باید توی این انواع وجود داشته باشن؟ چه‌موقع و چطور باید ازشون شئ نمونه بسازین و وقتی شئ ساختیم، باهاش چه‌کارهایی باید انجام بدیم؟ اینها واقعاً تصمیم‌های ساده‌ای نیستن و هیچ جواب واضح و قطعی خاصی براشون وجود نداره. «برنامه‌نویسی شئ‌گرا یه هنره»!

در واقعیت، وقتی که دارین برنامه‌نویسی iOS انجام میدین، انواع زیادی از اشیاء وجود دارن که شما باهاشون کار میکنین ولی مال شما نیستن و متعقل به Apple هستن. Swift خودش چندین نوع شئ مفید و کاربردی داره مثل String و Int و... و همچنین وقتی دستور import UIKit رو مینویسین، تعداد «بسیار زیادی» از انواع شئ وارد برنامه‌ی شما میشه که هرکدوم بخشی از برنامه‌ی شما رو زنده میکنن. شما هیچکدوم از این انواع رو نساختین، بنابراین طراحی اونها مشکل شما نیست؛ درعوض باید یاد بگیرین که چطور ازشون استفاده کنین. انواع شئ داخلی Apple طوری طراحی شدن که قابلیت‌های «عمومی» رو که هر برنامه ممکنه بهشون نیاز داشته باشه، در دسترس شما بگذارن. در همین زمان، برنامه‌ی شما احتمالاً نیاز به کارآیی‌های «اختصاصی» خواهد داشت که مختص هدف همون برنامه هستن و باید انواع شئ اختصاصی خودتون رو برای رسیدن به اون هدف طراحی کنین.

طراحی یه برنامه‌ی شئ‌گرا باید برپایه‌ی یه درک عمیق و کامل از «ذات اشیاء» انجام بشه. شما میخواین انواع مختلفی تعریف کنین که مجموعه‌ی صحیحی از قابلیت‌ها (رفتارها یا متدها) و داده‌ها (خصوصیات یا فیلدها) رو شامل بشن. بعداً وقتی که از این انواع، شئ نمونه میسازین، میخواین که مطمئن بشین اشیاء شما طول‌عمر صحیح و قابلیت دید مناسب نسبت‌به سایر اشیاء داشتن باشن و بتونن با همدیگه ارتباط مناسبی برقرار کنن.

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

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

بعنوان یه مثال، شیئی رو تصور کنید که که وظیفه‌اش پیاده‌سازی یه پُشته (Stack) هست (میتونه یه شئ از کلاسی به‌اسم Stack باشه که شما ساختین). یه پشته ساختار خاصی برای نگهداری داده‌هاست که مجموعه‌ی اطلاعات رو با ترتیب LIFE (مخفف Last In, First Out) نگهداری میکنه. این شئ فقط به دو پیام پاسخ میده: push و pop. دستور push یعنی یه داده که دریافت کردیم رو به مجموعه اضافه کنیم و pop هم یعنی آخرین داده‌ای که به مجموعه‌ی داده‌هامون اضافه شده بود رو برگردونیم و از مجموعه حذف کنیم. این ساختار دقیقاً شبیه پشته‌ای از بشقاب‌هاست که روی هم قرار گرفتن: بشقاب‌ها یکی پس‌از دیگری روی هم قرار میگیرن و از همون بالا هم اونها رو برمیداریم. بنابراین برای برداشتن اولین بشقابی که گذاشتیم، باید تمام بشقاب‌هایی که بعداً اضافه‌شدن رو یکی‌یکی برداریم.

هر شئ نمونه از نوع Stack در این مثال، به‌خوبی گردآوری قابلیت‌ها رو نشون میده چون دنیای بیرون هیچ‌چیزی درمورد نحوه‌ی پیاده‌سازی پشته در نوع Stack نمیدونه. میتونه یه آرایه، یه لیست پیوندی یا هر روش پیاده‌سازی دیگه باشه. هر بخشی از برنامه که از این شئ استفاده میکنه (مشتریان شئ که پیام‌های push و pop رو براش میفرستن) هیچ‌چیزی درمورد این ساختار نمیدونن و اهمیتی هم نمیدن چون خود شئ این کارها رو داره انجام میده و مثل یه پشته عمل میکنه. این‌موضوع برای برنامه‌نویس هم خوبه چون میتونه درحین توسعه‌ی برنامه، با ایمنی کامل یه پیاده‌سازی رو با یکی دیگه که بنظرش کارآمدتر میاد، عوض کنه بدون اینکه به کل ساختار برنامه و مکانیزم کارکردش آسیبی وارد بشه.

شئ پشته‌ی ما نگهداری وضعیت رو هم به‌خوبی نشون میده چون فقط یه گذرگاه برای داده‌های پشته نیست؛ بلکه «خودش» داده‌های پشته است. اشیاء دیگه به پشته فقط ازطریق اتصال به شئ دسترسی پیدا میکنن، اونم فقط از کانال‌هایی که شئ در اختیارشون گذاشته. داده‌های پشته به‌شکل کارآمدی داخل شئ پشته پنهان و ذخیره‌سازی شدن و هیچ‌کسی بجز خود شئ اونها رو نمیبینه. تنها کاری که بقیه‌ی اشیاء میتونن انجام بدن، push یا pop هست. ازطرفی اگه چند شئ پشته داشتیم باشیم، هرکدوم داده‌های پشته‌ی خاص خودشون رو دارن و درنتیجه وضعیت اختصاصی خودشون رو هم خواهند داشت و ارسال پیام push یا pop برای یه شئ پشته، تغییری در وضعیت (داده‌های) یه شئ دیگه‌ی پشته ایجاد نمیکنه.

مجموعه تمام پیام‌هایی که برای هر نوع شئ میتونیم بفرستیم، API (مخفف Application Programming Interface) یا رابط کاربری برنامه‌نویسی اون نوع نام داره و شبیه فهرست یا منویی از چیزهایی هست که میتونین از این نوع شئ بخواین تا انجام بده. انواع شئ شما کدتون رو تقسیم‌بندی میکنن. API اونها زیربنای ارتباط بین این تقسیم‌بندی‌ها رو مشخص میکنن. همین موضوع درمورد اشیائی که شما طراحی نکردین هم وجود داره. مستندات Cocoa شرکت Apple فهرست بزرگی از APIهای اشیاء مختلف رو شامل میشه. برای مثال، اگه میخواین بدونین چه پیام‌هایی رو میشه برای یه نمونه‌ی NSString بفرستین، کافیه مستندات کلاس NSString رو بخونید. توی اون صفحه، فهرست واقعاً بزرگی از خصوصیات و متدها وجود داره و درنتیجه به شما میگه که یه شئ NSString چه کارهایی میتونه انجام بده و بنابراین، به شما تمام اطلاعاتی که درخصوص نحوه‌ی استفاده از NSString توی برنامه‌ی خودتون لازم دارین رو میده.

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

هرچند بطور پیشفرض، هیچ شیئی وجود نداره. توی این مثال ما چند نوع شئ تعریف کردیم ولی هیچ نمونه‌ای ازشون نساختیم:
import UIKit
 
var one = 1
 
func changeOne() {
    let two = 2
    func sayTwo() {
        print(two)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
    one = two
}
 
class Student {
    var name = "ali"
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}
 
struct Person {
    var name = "mina"
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}
 
enum WeekDay {
    let days : String {
        return "Friday"
    }
    func sayName() {
        print(name)
    }
    class MyClass {}
    struct MyStruct {}
    enum MyEnum {}
}

اگه این برنامه رو اجرا کنیم، انواع شیئی که ساختیم از لحظه‌ی اجرا وجود خواهند داشت ولی این تنها چیزیه که وجود داره. ما دنیایی از امکانات بالقوه ساختیم: انواعی از اشیاء که «میتونن وجود داشته باشن». در چنین دنیایی، هیچ‌چیزی واقعاً اتفاق نمیفته.

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

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

طرح‌ریزی اینکه چطور میخواین نمونه‌ها رو بسازین و با طول‌عمر و نحوه‌ی ارتباطشون با سایر نمونه‌ها کار کنین، ممکنه یکم شما رو بترسونه. خوشبختانه در دنیای واقعی، وقتی که دارین یه برنامه‌ی iOS مینویسین، فریمورک Cocoa یکسری ساختارهای اسکلت‌بندی پروژه رو از ابتدا براتون فراهم میکنه. قبل‌از اینکه حتی یک‌خط کد بنویسید، خود فریمورک تضمین میکنه که برنامه‌ی شما وقتی اجرا بشه، یکسری اشیاء نمونه داره که درطول حیات برنامه اعتبار دارن و با این‌کار، پایه‌ی خوبی برای رابط کاربری برنامه فراهم میکنه و به شما یه مکان اولیه برای قراردادن نمونه‌های خودتون و اختصاص طول‌عمر مناسب بهشون رو ارائه میکنه.

جمع‌بندی و نتیجه‌گیری
درزمانی که داریم ساخت یه برنامه‌ی شئ‌گرا رو توی ذهنمون مجسم میکنیم تا یه کار خاص رو برامون انجام بده، باید «ذات اشیاء» همیشه توی ذهنمون باشه. دو مفهوم به‌نام «انواع شئ» و «نمونه‌ها» وجود دارن. یه نوع، مجموعه‌ای از متدهای که مشخص میکنه تمام نمونه‌های اون نوع میتونن انجام بدن (گردآوری قابلیت‌ها). نمونه‌های مختلف از یک نوع مشابه، فقط توی مقادیر و خصوصیاتشون با هم فرق دارن (نگهداری وضعیت). بنابراین ما باید با این دیدگاه طراحی برنامه‌مون رو انجام بدیم. اشیاء یه ابزار برای سازماندهی کدها هستن: مجموعه‌ای از جعبه‌ها برای نگهداری کدی که یه کار مشخص رو انجام میده. همچنین یه ابزار مفهومی هم محسوب میشن. برنامه‌نویس مجبوره که به اونها درقالب اشیاء مستقل نگاه کنه و اهداف و رفتار برنامه رو به وظایف کوچکتر و مجزا از هم تفکیک کنه و هر وظیفه رو بعهده‌ی یه شئ مناسب بگذاره.

در همین حال، باید حواسمون باشه که اشیاء ما، جزیره‌های جدا از هم نیستن. اشیاء میتونن با همدیگه همکاری کنن و ارتباط داشته باشن؛ یعنی برای همدیگه پیام بفرستن. روش‌هایی که خطوط مختلف ارتباطی رو میتونیم ترسیم کنیم، قابل شمارش نیست. رسیدن به یه ترکیب مناسب (معماری برنامه) برای ارتباط مناسب و تنگاتنگ و کارآمد بین اشیاء، یکی‌از بزرگترین چالش‌های برنامه‌نویسی شئ‌گرا محسوب میشه که به‌مرور با افزایش دانش شما در این زمینه و کسب تجربه در برنامه‌نویسی، به یک مهارت باارزش برای شما تبدیل میشه. توی برنامه‌نویسی iOS، فریمورک Cocoa (کاکائو) بعنوان یه شتاب‌دهنده برای شما عمل میکنه که مجموعه‌ی ابتدایی از انواع شئ و یک معماری ساده و اولیه و درعین‌حال کاربردی رو در اختیارتون میگذاره.

استفاده‌ی کارآمد از برنامه‌نویسی شئ‌گرا برای وادارکردن یه برنامه به انجام کاری که مدنظر شماست، همزمان با نگهداری کد بصورت تمیز و قابل نگهداری و توسعه، خودش به‌تنهایی یکی از مهارت‌های انکارنشدنی یه برنامه‌نویس محسوب میشه. مهارت‌های شما به‌مرور با کسب تجربه افزایش پیدا میکنن. در انتهای این بخش از آموزشمون، پیشنهاد میکنم دو کتاب قدیمی ولی باارزش رو در این زمینه مطالعه کنید. کتاب Refactoring (اصلاح و بهینه‌سازی کد بدون ایجاد مشکل در اجرای برنامه) نوشته‌ی Martin Fowler (انتشارات Addison-Wesley در سال 1999) به شما توضیح میده که چرا نیاز به بازنویسی متدهای خاصی از برخی از کلاسهای خودتون دارین و چطور این‌کار رو بدون ترس و نگرانی بابت بروز مشکل در کارکرد برنامه انجام بدین. کتاب Design Patterns (الگوهای طراحی) نوشته‌ی Erich Gamma و Richard Helm و Ralph Johnson و John Vlissides (که به‌اسم Gang of Four یا GoF هم شناخته میشن)، مرجع کامل تعریف معماری برنامه‌های شئ‌گرا محسوب میشه که تمام راه‌هایی که میتونین اشیائتون رو سازماندهی کنین، همراه‌با قدرت‌ها و اطلاعات کافی از مزایا و معایب هر روش نسبت به روش‌های دیگه (و حتی ترکیب این روش‌ها) رو به شما آموزش میده. این کتاب هم توسط انتشارات Addison-Wesley در سال 1994 منتشر شده.
تشکر شده توسط: meysam1366




کاربران در حال بازدید این موضوع: 1 مهمان