آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

آموزش مبانی و مفاهیم اصلی Swift

آموزش برنامه نویسی IOS بر اساس Swift – آموزش مبانی و مفاهیم اصلی Swift

اولین آموزش در قالب یک فایل Swift playground همراه با آموزش و راهنمایی لازم ارائه می شود. playground یک نوع فایل است که به شما اجازه می دهد در محیط برنامه نویسی Xcode با کد تعامل داشته، آن را تغییر دهید و نتایج را مستقیما مشاهده نمایید. فایل های playground برای یادگیری و آزمایش مطالب بسیار مناسب بوده و فایلی که در این درس ارائه شده به شما امکان می دهد تا با مفاهیم پایه ای Swift آشنا شوید.

آنچه خواهید آموخت

با مطالعه ی کامل مبحث قادر خواهید بود:

1. فرق بین ثوابت (constant) و متغیرها را بدانید.

2. بدانید چه هنگام از اعلان نوع به صورت ضمنی (implicit declaration) و چه هنگام از اعلان نوع به صورت صریح (explicit declaration) استفاده نمایید.

3. با فایده ی استفاده از optional ها و optional binding (ارسال پارامترهایی از نوع optional) آشنا شوید.

4. فرق بین optional ها و implicitly unwrapped optional ها را بدانید.

5. هدف استفاده از حلقه و دستورات شرطی را درک کنید.

6. با استفاده از switch امکان انتخاب بین چندین گزینه در صورت بر قرار بودن شرط خاص را فراهم آورید (ورای شرط های باینری که در صورت برقرار بودن شرط می توان از بین دو گزینه انتخاب انجام داد).

7. با استفاده از عبارات where، محدودیت های (constraint) بیشتری در دستورات شرطی اعمال نمایید.

8. فرق بین تابع (function)، متد (method) و initializer را تشخیص دهید.

9. فرق بین class، structure و enumeration را بدانید.

10. ساختار نگارشی یا سینتکس ساده برای ارث بری (inheritance) از کلاس دیگر و پیروی از الگوی پیاده سازی یا protocol خاص برای ساختارهای class، structure و enumeration را بدانید.

11. تشخیص نوع هایی که به صورت ضمنی اعلان شده اند و استفاده از امکان option-click جهت کسب اطلاعات بیشتر در محیط کاری Xcode.

12. وارد (import) کردن کتابخانه ی UIKit و استفاده از کلاس های آن در پروژه.

انواع داده ای (Basic type) در Swift

Constant (ثابت) مقداری است که پس از اعلان، ثابت باقی می ماند. این در حالی است که مقدار variable پس از تعریف قابل تغییر می باشد. constant را immutable نیز می گویند، بدین معنی پس از تعریف، امکان تغییر (مقدار) آن وجود ندارد. اما variable یا متغیر را mutable یا به اصطلاح قابل تغییر می نامند (در بخش های مختلف برنامه مقدارش قابل تغییر است).

چنانچه از عدم تغییر مقدار در سرتاسر کد خود اطمینان دارید، در آن صورت بهتر است بجای متغیر از ثابت یا constant استفاده نمایید.

جهت تعریف constant از کلیدواژه ی let و به منظور تعریف متغیر از var استفاده کنید:

var myVariable = 42
myVariable = 50
let myConstant = 42

هر ثابت یا متغیری که در Swift تعریف می کنید، یک نوع دارد. اما لازم نیست همیشه نوع داده ای را به صورت صریح مشخص نمایید. با مقداری که به متغیر یا ثابت تخصیص می دهید، در واقع نوعش را نیز به کامپایلر اعلان می کنید (با انتساب مقدار به ثابت یا متغیر تعریف شده، به کامپایلر اجازه می دهید نوع آن را حدس بزند). در مثال بالا، خط دوم کد، کامپایلر خود به نوع متغیر myVariable پی می برد (و آن را به عنوان integer می شناسد) چرا که مقدار اولیه ی آن، که در خط اول مشخص شده، از نوع integer است. از این قابلیت کامپایلر به عنوان type inference (حدس یا استنتاج نوع داده ای) یاد می شود. پس از اعلان نوع ثابت یا متغیر، امکان تغییر نوع آن وجود ندارد.

اگر مقدار اولیه، اطلاعات لازم درباره ی متغیر یا ثابت ارائه نکند (یا اساسا هیچ مقدار اولیه ای وجود نداشته باشد)، می توانید نوع را پس از اسم متغیر و دو نقطه ذکر نمایید.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

امتحان و تجربه کنید

داخل محیط Xcode، جهت اطلاع از نوع (تشخیص داده شده توسط کامپایلر)، بر روی اسم ثابت یا متغیر option-click نمایید. می توانید عملیات ذکر شده را بر روی ثوابت فوق اجرا کنید.

مقادیر هیچگاه به صورت ضمنی (از نوعی) به نوع دیگر تبدیل نمی شوند. در صورت نیاز به تبدیل نوع مقدار مورد نظر، کافی است (به صورت صریح) نمونه ای از نوع دلخواه را ایجاد نمایید. در نمونه ی زیر، مقدار یک ثابت را از نوع int به string تبدیل می کنیم:


let label = "The width is "
let width = 94
let widthLabel = label + String(width)

امتحان و تجربه کنید

تبدیل نوع به string را از آخرین خط کد حذف نمایید. با چه خطایی مواجه می شوید؟

راه آسان تری نیز برای اضافه کردن مقادیر در رشته ها وجود دارد: داخل رشته ی مورد نظر یک "\" تایپ کرده، سپس مقادیر دلخواه را از طریق پرانتز به داخل رشته ی مورد نظر تزریق کنید. از این پروسه تحت عنوان string interpolation یا درج مقدار جدید در رشته یاد می شود.

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

برای کار با متغیرهایی که ممکن است مقدار داشته یا نداشته باشد، می توانید از optional ها استفاده نمایید. optional متغیری است که یا مقداری دارد و یا به نشانه ی تهی بودن حاوی nil می باشد، به عبارتی دیگر یا مقدار دارد یا از مقدار تهی می باشد. جهت اعلان یک متغیر به عنوان optional، کافی است یک علامت "?" پس از نوع مقدار تایپ کنید.

let optionalInt: Int? = 9

برای استخراج (و دسترسی به) مقدار (اصلی و اولیه ی) یک متغیر optional، لازم است آن را unwrap کنید. در مباحث آینده با نحوه ی unwrap کردن optional ها آشنا خواهید شد، اما در خلاصه باید گفت که برای دسترسی به مقدار optional از عملگر "!" استفاده می شود. لازم به ذکر است که شما تنها زمانی باید از عملگر مزبور استفاده کنید که مطمئن باشید مقدار اصلی یا زیرین nil نیست.


let actualInt: Int = optionalInt!

Optional ها در Swift کاربرد زیادی دارند و در مواقعی که ممکن است مقداری وجود داشته یا نداشته باشد، می توانند فوق العاده مفید واقع شوند. از optional ها می توان به خصوص برای تبدیل نوع به صورت آزمایشی بهره گرفت.

var myString = "7"
var possibleInt = Int(myString)
print(possibleInt)

در این کد، مقدار متغیر possibleInt برابر 7 است چرا که نوع متغیر myString از رشته به عدد صحیح تبدیل شده است و پس از عملیات تبدیل حاوی مقدار تبدیل شده می باشد. اما اگر myString را نوعی تعریف کنید که قابل تبدیل به integer نباشد، آنگاه مقدار متغیر possibleInt، nil یا تهی خواهد بود.

myString = "banana"
possibleInt = Int(myString)
print(possibleInt)

آرایه یا array نوع داده ای است که مجموعه ای مرتب از آیتم ها را در خود جای می دهد. برای ایجاد آرایه از [] استفاده نمایید و برای دسترسی به المان های داخل آن، اندیس المان مورد نظر را در علامت [] ذکر کنید. اندیس آرایه از 0 آغاز می شود.

var ratingList = ["Poor", "Fine", "Good", "Excellent"]
ratingList[1] = "OK"
ratingList

جهت ایجاد آرایه ی تهی، کافی است از initializer استفاده کنید. به زودی به شرح initializer خواهیم پرداخت.

// Creates an empty array.
یک آرایه ی خالی تعریف می کند
let emptyArray = [String]()

همان طور که مشاهده می کنید، در کد بالا از comment استفاده شده است. Comment یا توضیحات یک قطعه متن (در کد برنامه) است که به عنوان بخشی از برنامه کامپایل یا ترجمه نمی شود اما توضحیاتی و اطلاعات مفیدی را درباره ی بخش های مختلف کد ارائه می دهد. comment ها به دو دسته تقسیم می شوند: 1. Comment های تک خطی که پس از // درج می شود 2. Comment های چند خطی که بین */ و /* قرار می گیرند. هر دو نمونه ی نام برده را در بخش های مختلف کد برنامه مشاهده خواهید کرد.

Implicitly unwrapped optional یک متغیر از نوع optional است که می توان از آن مانند یک متغیر nonoptional و عادی استفاده کرد، بدون اینکه لازم باشد هر بار برای دسترسی به مقدارش (مقدار متغیر optional) آن را unwrap کنید. دلیلش این است که یک implicitly unwrapped optional پس از اعلان و تنظیم مقدار اولیه، همیشه حاوی مقدار در نظر گرفته می شود، هرچند مقدار آن می تواند تغییر کند. برای تعریف متغیرهایی از نوع Implicitly unwrapped optional، بایستی بجای "?" از "!" استفاده نمایید.


var implicitlyUnwrappedOptionalInt: Int!
کنترل جریان یا روند اجرای برنامه (Control Flow) 

Swift از دو نوع دستور (دستورات شرطی و حلقه های تکرار) برای کنترل جریان اجرای برنامه بهره می گیرد. دستورات شرطی یا Conditional statements نظیر if و switch، قبل از اجرای دستور معین، بررسی می کنند آیا یک شرط صحیح/برقرار است یا خیر. بدین معنی که قبل از اجرای دستور معین، ابتدا شرط را ارزیابی می کنند و سپس در صورت درست بودن شرط (ارزیابی شرط به مقدار بولی true)، آن دستور را اجرا می کنند. و دیگری حلقه های تکرار نظیر for-in و while که یک قطعه کد یا مجموعه دستور را مادامی که شرط خاصی برقرار است، تکرار می کنند.

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

let number = 23
if number < 10 {
print("The number is small")
} else if number > 100 {
print("The number is pretty big")
} else {
print("The number is between 10 and 100")
}

امتحان و تجربه کنید

مقدار number را به عدد صحیح دیگری تغییر داده و پس از اجرای کد ببینید کدام دستور اجرا می شود (کدام رشته چاپ می شود).

دستورات را می توان جهت تعریف رفتار و عملیات پیچیده، داخل هم قرار داد یا به اصطلاح تودرتو (nest) کرد. در زیر یک دستور if..else را مشاهده می کنید که در بدنه ی یک حلقه ی for-in قرار داده شده است (این حلقه داخل آرایه چرخیده و تک تک آیتم های آن را به ترتیب می خواند).

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)

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

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}

امتحان و تجربه کنید

مقدار متغیر optionalName را به nil تغییر داده و کد را اجرا کنید. چه مقداری را در خروجی دریافت می کنید؟ حال یک دستور else اضافه کنید که در صورت nil بدون مقدار optionalName، مقدار متفاوتی را در greeting قرار دهد.

در صورتی که مقدار nil باشد، شرط غلط بوده و دستور داخل {} اجرا نمی شود. در غیر این صورت مقدار متغیر از نوع optional استخراج (unwrap) شده و به ثابت بعد از let (name) تخصیص داده می شود. در نتیجه مقدار استخراج شده داخل قطعه کد مورد نظر برای استفاده در دسترس قرار می گیرد.

می توانید با تنها یک دستور if همزمان چندین مقدار را bind کنید.

می توان با اضافه کردن دستور where به یک case یا مورد انتخاب، قیود بیشتری اعمال نموده و نتیجه را محدودتر کرد. برای مثال در نمونه ی زیر، دستور if تنها زمانی اجرا می شود که binding تمامی مقادیر با موفقیت انجام شده و تمامی شرایط برآورده شوند.

var optionalHello: String? = "Hello"
if let hello = optionalHello where hello.hasPrefix("H"), let name = optionalName {
greeting = "\(hello), \(name)"
}

ساختار Switch در زبان برنامه نویسی Swift از قدرت و امکانات ویژه ای برخوردار است. یک دستور switch از انواع داده ای و همچنین طیف وسیعی از عملیات مقایسه ای پشتیبانی می کند – تنها به نوع داده ای int و بررسی برابری مقادیر محدود نمی شود. در مثال زیر، دستور switch مقدار متغیر vegetable را با مقادیر case ها، مقایسه کرده و سپس دستور مرتبط با مقدار منطبق را اجرا می کند.

let vegetable = "red pepper"
switch vegetable {
case "celery":
let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
let vegetableComment = "Is it a spicy \(x)?"
default:
let vegetableComment = "Everything tastes good in soup."
}

امتحان و تجربه کنید

دستور default را حذف کنید. با چه خطایی مواجه می شوید؟

ببینید چگونه از let برای تخصیص مقدار case یا مورد منطبق به یک constant استفاده شده است. مشابه دستور if، می توان با افزودن عبارت شرطی where به یک case، قیود بیشتری اعمال کرده و نتیجه را محدودتر نمود.

برخلاف دستور if، یک case می تواند چندین شرط (که توسط ویرگول از هم جدا شده اند) داشته باشد و حتی اگر یکی از شروط آن صادق باشد، آنگاه دستور مرتبط اجرا خواهد شد. پس از اجرای دستور موجود در case منطبق، برنامه از ساختمان switch خارج می شود و اجرای برنامه به case بعدی ادامه نمی یابد، بنابراین لازم نیست پس از هر case یک دستور break درج نمایید (به صورت صریح از قطعه کد switch خارج شوید).

استفاده از دستور default در بیشتر سناریوها ضروری است. با استفاده از default این اطمینان بدست می آید که در صورت برقرار نبودن شرط هیچ یک از case ها (برابر نبودن مقدار داخل پرانتز با مقدار هیچ یک از case ها)، یک دستور در ساختمان switch اجرا می گردد (با استفاده از این دستور می توانید برای مثال یک پیغام مرتبط به صفحه نمایش ارسال نمایید).

در واقع در ساختار چند انتخابی switch، باید از دستور default استفاده کنید، مگر اینکه مطمئن باشید مقدار داخل پرانتز و مورد مقایسه با حداقل یکی از case ها منطبق است.

می توان با استفاده از یک Range، اندیس را بین دو بازه نگه داشته و در آن پیمایش نمود. جهت ایجاد بازه ای از اندیس ها می توان از عملگر "..<" استفاده نمود.

var firstForLoop = 0
for i in 0..<4 {
firstForLoop += i
}
print(firstForLoop)

این عملگر عدد بزرگتر را شامل نمی شود، بنابراین بازه از 0 شروع شده و تا 3 ادامه می یابد که در کل 4 بار دستور اجرا می گردد. می توانید با استفاده از "..." یک بازه ایجاد کنید که مقدار دو طرف بازه را دربرمی گیرد (شامل می شود).

var secondForLoop = 0
for _ in 0...4 {
secondForLoop += 1
}
print(secondForLoop)

این بازه از 0 شروع شده و تا عدد 4 را دربرمی گیرد که در کل به پنج بار اجرای دستور توسط حلقه منتهی می گردد. علامت "_" بیانگر یک wildcard است و شما می توانید از آن زمانی استفاده کنید که نیازی به آگاهی از گام جاری تکرار (در حال اجرا) حلقه ندارید (به عبارت دیگر برای شما مهم نیست حلقه چند بار اجرا شده است و در حال حاضر کدام گام حلقه در حال اجرا است).

تشریح تفاوت بین function و method

Function یا تابع یک تکه کد نام گذاری شده با قابلیت استفاده ی مجدد است که از بخش های مختلف برنامه می توان آن را فراخوانی کرد. به عبارت دیگر عملیاتی که فکر می کنید چندین بار به آن نیاز پیدا خواهید کرد را در قالب یک تابع تعریف کرده، یک نام به آن تخصیص می دهید. سپس هر بار که به این عملیات نیاز داشتید می توانید آن را در برنامه ی خود فراخوانی کنید (دیگر نیازی نیست آن عملیات را هر بار تعریف کنید).

جهت تعریف یک تابع جدید، کافی است از کلیدواژه ی func استفاده نمایید. یک تابع می تواند چندین پارامتر ورودی داشته باشد که به صورت (نوع پارامتر:اسم پارامتر) name: Type داخل پرانتزهای آن نوشته می شوند. پارامترها در واقع اطلاعات اضافی هستند که به هنگام فراخوانی تابع باید به آن ارسال شوند. یک تابع می تواند مقدار بازگشتی یا خروجی داشته باشد که در swift پس از علامت "->" ذکر می شود. بدین وسیله مشخص می شود تابع پس از اجرا چه مقداری (از چه نوعی) را به عنوان خروجی تولید می کند. پیاده سازی (implementation) تابع نیز داخل بدنه ی آن {} قرار می گیرد.

func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}

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

greet("Anna", day: "Tuesday")
greet("Bob", day: "Friday")
greet("Charlie", day: "a nice day")

توابعی که داخل نوع خاصی (برای مثال کلاس) تعریف می شوند در اصطلاح method یا متد خوانده می شوند. متدها صریحا به نوعی که در آن تعریف می شوند وابسته هستند و تنها در آن نوع (یا یکی از زیرمجموعه ها و subclass های آن) قابل فراخوانی می باشند. در دستور switch مثال بالا، یک متد به نام ()hasSuffix را می بینید که بر روی نوع string تعریف شده است. این مثال در زیر مجددا عنوان شده است:

let exampleString = "hello"
if exampleString.hasSuffix("lo") {
print("ends in lo")
}

همان طور که در مثال مشاهده می کنید، متد با عملگر "." (بر روی متغیر) فراخوانی شده است. زمانی که یک متد را صدا می زنید، اولین آرگومان را بدون ذکر اسم آن پاس داده و مقادیر آرگومان بعدی را همراه با اسم آن ها به متد ارسال می کنید. برای مثال، متد insert() که بر روی متغیری به نام array از نوع آرایه (Array) فراخوانی می شود، دو پارامتر می گیرد ولی شما فقط اسم آرگومان دوم را مشخص می کنید.

var array = ["apple", "banana", "dragonfruit"]
array.insert("cherry", atIndex: 2)
array

تشریح مفهوم class و initializer

در برنامه نویسی شی گرا، رفتار یک برنامه تا حد زیادی به تعامل و تبادل اطلاعات میان آبجکت ها (object) بستگی دارد. یک object در واقع نمونه ای از یک class است. کلاس نیز یک طرح کلی یا الگو برای ساخت آبجکت می باشد. در یک کلاس، آبجکت ها اطلاعات مورد نیاز درباره ی خود را در قالب property ها ذخیره کرده و رفتار خود را با استفاده از متدها تعریف می کنند.

جهت تعریف یک کلاس جدید، ابتدا کلیدواژه ی class و سپس اسم دلخواه را تایپ نمایید. یک property درست مانند ثابت یا متغیر تعریف می شود، با این تفاوت که تعریف آن در سطح (context) کلاس انجام می شود (property داخل کلاس تعریف می شود).

تعریف متد و تابع نیز به همان صورت انجام می شود (متد در بستر کلاس تعریف می شود، اما تابع چنین نیست). مثال زیر یک کلاس به نام shape، یک property به نام numberOfSides و یک متد به نام simpleDescription() تعریف می کند.

class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}

به منظور ایجاد نمونه ای (یا آبجکت) از یک کلاس، کافی است یک پرانتز باز و بسته بعد از اسم کلاس درج نمایید. حال جهت دسترسی به property ها/method های نمونه ی ایجاد شده از کلاس، اسم نمونه ی کلاس و به دنبال آن عملگر نقطه و در پایان اسم property/method دلخواه را تایپ نمایید. در این مثال، shape یک نمونه یا آبجکت از کلاس Shape است که با پیروی از ساختار نگارشی ذکر شده به اعضای آن (متد و پراپرتی) دسترسی پیدا می کنید.

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

این کلاس به یک عضو دیگر نیاز دارد و آن initializer است. initializer یک متد است که نمونه ای از یک کلاس را برای استفاده آماده ساخته، تک تک property های آن را مقداردهی اولیه می کند و امکان هرگونه تنظیم دیگری را فراهم می آورد. برای ایجاد initializer، کافی است از کلیدواژه ی init استفاده نمایید. این مثال یک کلاس جدید به نام NamedShape تعریف کرده، سپس با استفاده از initializer خود (متد سازنده یا مقدار دهنده ی اولیه متغیرهای کلاس)، یک property به نام name را مقداردهی اولیه می کند.

class NamedShape {
var numberOfSides = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}

با استفاده از کلیدواژه ی self (معادل this در C#)، در حقیقت عملا بین (property) متغیر name و آرگومان ارسالی به همان نام (آرگومان name) به initializer تمایز قائل می شوید. هر property ای که تعریف می کنید، باید مقداردهی نمایید (حال این مقدار دهی یا در تعریف آن property انجام می شود مانند numberOfSides و یا داخل initializer مانند name).

برای فراخوانی یک initializer، ذکر کلیدواژه ی init ناصحیح است. روش صحیح آن عبارت است از ذکر اسم کلاس و سپس درج پرانتز باز، آرگومان های مورد نظر و در نهایت پرانتز بسته. زمانی که شما یک initializer را صدا می زنید، تمامی آرگومان ها را همراه با اسمشان به آن (initializer) پاس می دهید.

let namedShape = NamedShape(name: "my named shape")

کلاس ها رفتار خود را از کلاس والد/parent به ارث می برند. کلاسی که رفتارش را از کلاس دیگری به ارث می برد، فرزند/زیرمجموعه یا subclass آن کلاس شناخته می شود. کلاسی هم که رفتارش را به کلاس دیگری به ارث می دهد، کلاس والد/ارشد یا superclass خوانده می شود. در ارث بری، ابتدا اسم کلاس فرزند و پس از دو نقطه، اسم کلاس والد ذکر می شود. به عبارت دیگر جهت ارث بری، ابتدا اسم کلاس subclass، سپس دو نقطه ":" و در پایان اسم superclass درج می شود. یک کلاس (subclass) تنها می تواند از یک superclass ارث بری کند و متعاقبا آن superclass خود می تواند از یک superclass دیگر ارث بری داشته باشد که از آن به عنوان class hierarchy (زنجیره ی ارث بری) یاد می شود.

متدهایی که در یک subclass، پیاده سازی متد کلاس superclass را بازنویسی یا override می کنند، با کلیدواژه ی override نشانه گذاری می شوند. اگر بدون استفاده از کلیدواژه ی override، سعی کنید بدنه یا پیاده سازی متد superclass را بازنویسی نمایید، compiler بلافاصله ایراد می گیرد و آن را به عنوان خطا تشخیص می دهد . کامپایلر همچنین متدهایی که با override علامت گذاری می شوند اما به معنای واقعی بازنویسی در آن ها صورت نمی گیرد را به راحتی شناسایی می کند.

مثال زیر یک کلاس به نام Square را تعریف می کند که از NamedShape ارث بری دارد (کلاس زیرمجموعه ای از NamedShape می باشد).

مثال:

class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let testSquare = Square(sideLength: 5.2, name: "my test square")
testSquare.area()
testSquare.simpleDescription()

همان طور که می بینید متد initializer از کلاس Square، سه کار زیر را انجام می دهد:

1. مقداردهی property هایی که در کلاس Square تعریف شده است.

2. فراخوانی متد initializer از کلاس NamedShape.

3. ویرایش مقدار property های تعریف شده در کلاس (والد) NamedShape. تمامی تنظیمات لازم هم که از متدها، getter ها و setter ها استفاده می کنند، باید در این مرحله انجام شود.

گاهی مقداردهی اولیه ی یک آبجکت باید (یا ممکن است) با شکست مواجه شود، مانند زمانی که مقادیر ارائه شده به عنوان آرگومان از یک بازه ی مشخص خارج هستند یا هنگامی که داده های مورد انتظار (اطلاعاتی که باید وجود داشته باشند) در دسترس نیستند. Initializerهایی که در مقداردهی اولیه ی یک آبجکت با شکست مواجه می شوند را در اصطلاح failable initializer می نامند. failable initializer پس از (شکست در) عملیات مقداردهی اولیه می تواند nil بازگردانی نماید. جهت اعلان یک failable initializer از init? استفاده نمایید:

class Circle: NamedShape {
var radius: Double
init?(radius: Double, name: String) {
self.radius = radius
super.init(name: name)
numberOfSides = 1
if radius <= 0 {
return nil
}
}
override func simpleDescription() -> String {
return "A circle with a radius of \(radius)."
}
}
let successfulCircle = Circle(radius: 4.2, name: "successful circle")
let failedCircle = Circle(radius: -7, name: "failed circle")

initializer ها نیز کلیدواژه های اختصاصی خود را دارند. یک designated initializer به هیچ کلیدواژه ای نیاز ندارد.designated initializer همان متد init اصلی و اولیه ی کلاس است که می تواند property های غیر optional و فاقد مقدار را مقداردهی اولیه کند). متد نام برده به عنوان یکی از initializer های اولیه و اصلی کلاس ایفای نقش می کنند. تمامی initializer های داخل یک کلاس در نهایت بایستی designated initializer را صدا بزنند.

کلیدواژه ی convenience (در کنار کلمه ی کلیدی init ) نشانگر initializer های ثانوی است که برای افزودن رفتار جدید یا سفارشی سازی بکار می روند. convenience initializer ها نیز در نهایت می بایست designated initializer (initializer اصلی و اولیه ی کلاس) را صدا بزنند.

کلیدواژه ی required اعلان می کند که تمامی کلاس هایی که از کلاس والد ارث بری می کنند و initializer آن را دارند، باید نسخه ی اختصاصی خود از initializer مورد نظر را پیاده سازی کنند (البته در صورتی که کلاس مورد نظر متد init را پیاده سازی کند).

Type casting (تبدیل نوع) روشی است برای بررسی نوع یک آبجکت (یا نمونه از کلاس) و برخورد با آن شی گویی خود یک کلاس والد مجزا است و یا یک کلاس فرزند از جای دیگری در زنجیره ی ارث بری (class hierarchy) خود است.

یک ثابت یا متغیر از کلاسی با نوع معین، ممکن است در اصل به یک نمونه از کلاس فرزند اشاره داشته باشد (مقدارش را از آن گرفته باشد). در جایی که فکر می کنید این امر صدق می کند، می توانید با استفاده از عملگر تبدیل نوع، به کلاس فرزند downcastانجام دهید. به عبارت دیگر، از نمونه کلاس والد به کلاس فرزند تبدیل نوع انجام دهید (downcast: تبدیل آبجکتی به یک نوع از کلاس های فرزند آن. Downcast زمانی امکان پذیر است که متغیری از کلاس والد، مقداری را از کلاس فرزند می گیرد).

از آنجایی که احتمال شکست فرایند downcast وجود دارد، عملگر تبدیل به دو صورت کلی ارائه می شود. شکل optional آن، as?، یک مقدار optional از نوعی که می خواهید به آن تبدیل نوع انجام دهید، در خروجی برمی گرداند. شکل forced آن، as!، سعی می کند downcast را انجام داده و نتیجه را همزمان force-unwrap کند (محتوای نتیجه را به زور استخراج کند).

از "as?" زمانی استفاده کنید که از انجام موفقیت آمیز عملیات downcast اطمینان ندارید. این عملگر در خروجی همیشه مقداری از نوع optional را برمی گرداند. چنانچه downcast امکان پذیر نباشد، مقدار خروجی nil خواهد بود. بدین وسیله شما می توانید بررسی کنید آیا downcast موفقیت آمیز خواهد بود یا خیر.

از عملگر "as!" تنها زمانی استفاده کنید که از اجرای موفقیت آمیز عملیات downcast اطمینان کامل دارید. چنانچه سعی کنید با این عملگر تبدیل نوع به کلاس (class type) نامربوط و نامنطبق را انجام دهید، با خطای زمان اجرا (runtime) مواجه خواهید شد.

این مثال سعی می کند با بهره گیری از عملگر as? بررسی کند آیا یک shape (متغیر shape) در آرایه ای از shape ها از نوع square است یا triangle. سپس هر بار که حلقه در گام تکرار خود با shape یا مورد منطبق برخورد می کند، مقدار متغیرهای squares و triangles را یک واحد اضافه می کند و در پایان مقادیر مربوطه را از طریق دستور print چاپ می نماید.

class Triangle: NamedShape {
init(sideLength: Double, name: String) {
super.init(name: name)
numberOfSides = 3
}
}
let shapesArray = [Triangle(sideLength: 1.5, name: "triangle1"), Triangle(sideLength: 4.2, name: "triangle2"), Square(sideLength: 3.2, name: "square1"), Square(sideLength: 2.7, name: "square2")]
var squares = 0
var triangles = 0
for shape in shapesArray {
if let square = shape as? Square {
squares++
} else if let triangle = shape as? Triangle {
triangles++
}
}
print("\(squares) squares and \(triangles) triangles.")

امتحان و تجربه کنید

در کد بالا عملگر as? را با as! جایگزین کنید. چه خطایی رخ می دهد؟

تشریح مفهوم Enumeration ها و Structure ها در Swift

کلاس ها تنها راه های تعریف انواع داده ای در زبان شی گرای Swift نیستند (تنها بسترهای تعریف متغیر، property و متد نیستند). Enumeration ها و Structure ها کاربرد و قابلیت های مشابه به کلاس را دارند ،اما در شرایط متفاوتی به کار گرفته می شوند.

Enumerations یک نوع مشترک برای گروهی از مقادیر مرتبط تعریف کرده و به شما امکان می دهد در کد خود با این مقادیر به صورت type-safe کار کنید. (swift یک زبان است که جانب ایمنی نوع یا type safety را رعایت می کند؛ بدین معنی که اگر بخشی از کد برنامه ی شما انتطار مقداری از جنس رشته را داشته باشد، ویژگی type safety مانع از این می شود که به صورت تصادفی مقداری از نوع عدد صحیح را به آن ارسال کنید).

برای مثال می توان به وضعیت اتصال شبکه اشاره کرد که در آن چهار حالت unknown، disconnected، connecting، connected ممکن است. در این سناریو یک enum از نوع int تعریف می شود که مقدار خام آن از 1 شروع می شود و به همین ترتیب ادامه می یابد.

Enumeration ها در Swift، متد نیز می توانند داشته باشند. جهت تعریف Enumeration کافی است از کلیدواژه ی enum استفاده نمایید:


enum Rank: Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
func simpleDescription() -> String {
switch self {
case .Ace:
return "ace"
case .Jack:
return "jack"
case .Queen:
return "queen"
case .King:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.Ace
let aceRawValue = ace.rawValue

در مثال بالا نوع مقدار خام (مقدار از نوع رشته، عدد صحیح، کاراکتر) و تخصیص داده شده به ساختار enumeration از جنس int است، بنابراین بایستی فقط مقدار اولین عضو را مشخص نمایید. دیگر اعضای این ساختار به ترتیب مقداردهی می شوند (برای مثال اگر مقدار اولین عضو را 1 بدهید، مقادیر دیگر اعضا به ترتیب 3،2 و 4 خواهند بود).

نوع enum را می توان از جنس رشته یا عددی ممیز شناور (floating-point) نیز تعریف کرد.

جهت دسترسی به مقدار خام و اولیه ی اعضای ساختار enum، کافی است از property ای به نام rawValue استفاده نمایید.

می توان با استفاده از مقدار یکی از اعضای enum یک نمونه از آن ایجاد کرد. برای ساختن نمونه از enum از روی مقدار یکی از اعضای آن، می بایست متد init?(rawValue:) را بکار ببرید.


if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}

مقادیر عضو یک ساختار enum، مقادیر حقیقی هستند، نه صرفا روشی دیگر برای نوشتن مقادیر خام/raw value (از نوع اولیه همچون رشته، عدد صحیح و غیره..) آن ها. در حقیقت، در مواردی که مقدار خام معناداری وجود ندارد، شما هم ملزوم به ارائه ی مقدار نیستید.


enum Suit {
case Spades, Hearts, Diamonds, Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs:
return "clubs"
}
}
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

در مثال بالا عضوی به نام Hearts به دو روش مختلف مورد اشاره قرار گرفته است: زمانی که به ثابت hearts مقداری اختصاص داده شده، عضو Suit.Hearts، همان طور که می بینید، با نام کاملش مورد اشاره قرا گرفته چرا که نوع ثابت (constant) مورد نظر به صورت صریح مشخص نشده است. اما داخل ساختمان switch، عضو مزبور enumeration با شکل مختصر آن مورد اشاره قرار گرفته زیرا نوع self مشخصا از جنس suit است (از قبل می دانیم نوع آن suit است).

هرگاه که نوع مقدار از قبل مشخص باشد، می توانید (برای اشاره به آن) از شکل مختصر اسم آن عضو استفاده کنید.

Structure ها بسیاری از رفتارها و امکانات کلاس نظیر متد و initializer را پشتیبانی می کنند. یکی از تفاوت های عمده ی structure با کلاس در این است که به هنگام ارسال structure به بخش های مختلف کد، یک کپی از آن به کد مورد نظر فرستاده می شود. این در حالی است که کلاس با ارجاع یا reference به کد پاس داده می شود (بدین معنی که هرگونه تغییر در کلاس توسط کد، بر روی کلاس اصلی نیز اعمال می شود).

structure ها برای تعریف انواع داده ای سبک که به قابلیت هایی همچون ارث بری و تبدیل نوع نیاز ندارند، بسیار مناسب می باشد. جهت تعریف یک structure جدید کافی است از کلیدواژه ی struct استفاده نمایید:


struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
شرح مفهوم Protocol (الگوی پیاده سازی)

Protocol یک الگو برای پیاده سازی متدها، property ها و دیگر ملزومات مناسب برای انجام کار یا پیاده سازی قابلیت خاص می باشد. لازم به ذکر است که protocol هیچ پیاده سازیی در خصوص متدها، property ها و غیره ... ارائه نمی دهد، بلکه تنها نحوه ی پیاده سازی ملزومات نام برده را تعیین می کند. می توان گفت protocol همان interface در objective-c است.

این الگو سپس برای پیاده سازی اعضای (متدها، پرارپرتی ها و غیره ..) یک class، enumeration یا structure بکار گرفته می شود. هر یک از ساختارهای ذکر شده (class و enumeration یا structure) که الگو یا protocol را پیاده سازی کنند، در اصطلاح به آن conform (از آن الگو پیروی) می کنند.

برای تعریف یک protocol، از کلیدواژه ی protocol استفاده می کنیم:



protocol ExampleProtocol {
var simpleDescription: String { get }
func adjust()
}

نکته: دستور { get }، پس از متغیر simpleDescription در کد بالا، بیانگر این است که مقدار متغیر نام برده، فقط خواندنی است؛ بدین معنی که مقدارش را می توان خواند اما امکان تغییر در آن وجود ندارد.

Protocol ها می توانند ایجاب کنند که ساختارهایی که از آن ها پیروی می کنند (پیاده سازی اعضای خود را بر اساس آن الگو انجام می دهند) دارای instance property، instance method، type method، operator و subscript های خاصی باشند. در بالا نیز گفته شد که protocol یک الگو یا معرفی یک سری متد و property است. اگر برای آبجکت یا کلاسی protocol اعلان کنید، آن کلاس یا آبجکت ملزم به پیاده سازی متدها و property های تعیین شده توسط آن protocol می شود.

Protocol ها همچنین می توانند instance method ها و type method هایی که توسط ساختار مورد نظر پیاده سازی می شوند را تعیین کنند (به عبارت دیگر protocol می تواند ساختار تبعیت کننده را مجاب به پیاده سازی type method ها و instance method های خاصی بکند). این متدها به عنوان بخشی از تعریف protocol و بدون {} یا بدنه مشخص می شوند. Type method: متدهایی که در ساختار خاصی (class، enum یا structure) تعریف می شوند و به آن وابسته و مرتبط هستند. Instance method: متدهایی که به نمونه ی ایجاد شده از ساختار مورد نظر تعلق دارند و تمامی قابلیت های آن ساختار را پشتیبانی می کنند.

Class ها، structure ها و enumeration ها برای پیروی از protocol یا الگوی پیاده سازی خاص، اسم آن را پس از اسم خود و دو نقطه ذکر می کنند. هر ساختار می تواند از بی نهایت protocol پیروی کند. اسم protocol ها به صورت یک لیست تفکیک شده با ویرگول، پس از اسم ساختار مورد نظر عنوان می شوند.

چنانچه یک کلاس از کلاس والدی ارث بری کند، در آن صورت اسم کلاس والد باید پیش از اسم protocol ها لیست شود.

همان طور که می دانید یک ساختار زمانی کاملا از protocol معین پیروی می کند که تمامی ملزومات و اعضای تعیین شده در آن نظیر متدها، property ها و غیره را پیاده سازی کند.

در زیر، کلاس SimpleClass اعضای خود را بر اساس protocol یا الگوی ExampleProtocol پیاده سازی می کند (اعضای تعریف شده در الگوی نام برده، property ای به نام simpleDescription و متد adjust()، را پیاده سازی می کند).



class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

protocol ها نیز مانند دیگر نوع ها می توانند به عنوان آرگومان ارسال شوند، خروجی یک تابع باشند یا به متغیر معینی تخصیص داده شوند (به عبارت دیگر، protocol ها first class type هستند و با آن ها مانند دیگر نوع های نام گذاری شده همچون آرایه، رشته و غیره ... برخورد می شود).

برای مثال می توانید یک آرایه ExampleProtocol تعریف کنید و سپس تابع adjust() را برای تک تک نمونه های داخل آن فراخوانی نمایید (تمامی نمونه های داخل آن آرایه متد نام برده را که توسط protocol تعریف شده است، پیاده سازی می کنند).

Ali class SimpleClass2: ExampleProtocol { var simpleDescription: String = "Another very simple class." func adjust() { simpleDescription += " Adjusted." } } var protocolArray: [ExampleProtocol] = [SimpleClass(), SimpleClass(), SimpleClass2()] for instance in protocolArray { instance.adjust() } protocolArray

Swift و فریم ورک طراحی رابط کاربری Cocoa Touch

طراحی زبان Swift به گونه ای است که به راحتی و بی درد سر می توان از آن همراه با Cocoa Touch استفاده کرد. به عبارت دیگر این زبان شی گرا طوری تعبیه شده که به راحتی می تواند با Cocoa Touch همکاری داشته باشد. Cocoa Touch یک سری framework جهت طراحی اپلیکیشن برای سیستم عامل IOS است. با پیشرفت در مباحث بعدی این سری آموزش، داشتن فهم پایه ای از نحوه ی تعامل Swift با Cocoa Touch، کمک شایانی به شما در یادگیری برنامه نویسی برای IOS و توسعه ی اپلیکیشن می کند.

تا به اینجا ، منحصرا از نوع داده هایی که Swift standard library ارائه می دهد، در مثال های عملی خود استفاده کردید. Swift standard library یا کتابخانه ی متعارف زبان Swift، متشکل است از تعدادی نوع داده ای و امکانات اختصاصی که به صورت درون ساخته در این زبان برنامه نویسی تعبیه شده است. نوع های همچون آرایه/Array و رشته/String نمونه هایی از انواع داده ای هستند که در کتابخانه ی متعارف Swift یافت می شود.

Ali let sampleString: String = "hello" let sampleArray: Array = [1, 2, 3.1415, 23, 42]

امتحان و تجربه کنید

با Option-click بر روی نوع مورد نظر در محیط Xcode، می توانید اطلاعات مربوط به انواع داده ای موجود در کتابخانه ی Swift را مشاهده نمایید. بر روی String و Array در کد بالا، داخل محیط Xcode، Option-click کنید. چه اطلاعاتی به نمایش در می آید؟

در طول طراحی اپلیکیشن برای سیستم عامل IOS، از کتابخانه ی متعارف Swift بیشتر استفاده خواهید کرد.

یکی از framework های پرکاربرد در طراحی اپلیکیشن برای IOS، کتابخانه ی UIKit است. این framework کلاس های متعددی را برای کار با لایه ی UI و طراحی رابط کاربری برنامه ارائه می دهد.

جهت دسترسی و استفاده از کلاس های UIKit، کافی است آن را به صورت یک ماژول داخل فایل playground یا فایل Swift دلخواه وارد/import نمایید.

import UIKit

پس از وارد کردن UIKit، می توانید انواع داده ای مشخص شده در این framework را همراه با متدها، property های متناظر آن بکار ببرید.

let redSquare = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
redSquare.backgroundColor = UIColor.redColor()

بیشتر کلاس هایی که در مثال های عملی این سری آموزشی با آن ها برخورد می کنید از کتابخانه ی UIKit برگرفته می شوند، بنابراین دستور import را به کرات مشاهده خواهید کرد. با کسب این میزان دانش از Swift شما آماده اید که در مبحث بعدی یک اپلیکیشن کامل و کاربردی را طراحی و پیاده سازی کنید.

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

  • 4430
  •    8
  • تاریخ ارسال :   1395/07/12

دانشجویان گرامی اگر این مطلب برای شما مفید بود لطفا ما را در GooglePlus محبوب کنید
رمز عبور: tahlildadeh.com یا www.tahlildadeh.com
ارسال دیدگاه نظرات کاربران
شماره موبایل دیدگاه
عنوان پست الکترونیک

ارسال

آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

تمامی حقوق این سایت متعلق به آموزشگاه تحلیل داده می باشد .