structها و classهاstructها و classها دو مورد از بلوکهای اصلی برنامهنویسی Swift هستند. هر دوی آنها روشی برای گرد هم آوردن دادهها و توابع مرتبط در واحدهای خودبسندهای از کارکرد هستند. وقتی یک struct یا class تعریف میکنید، در واقع یک نوع جدید تعریف میکنید که در Swift به کار میآید، دقیقاً همانطور که از انواعی که تا حالا با آنها کار کردهاید استفاده میکنید، مانند Int و String.
structها و classها در Swift شباهتهای زیادی به هم دارند. در میان شباهتهای دیگر، هر دو میتوانند مقادیر را در «ویژگی»ها نگه دارند و کارکرد خود را از طریق «متد»ها فراهم کنند.
هر کدام قابلیتهای اضافی دیگری هم دارند که خارج از محدودهی این تمرین است.
struct و class
هر دو تقریباً به یک شکل تعریف میشوند. با کلیدواژهی مناسب شروع میشوند و پس از آن اسم نوعی میآید که تعریف میشود، سپس بدنهی struct یا class در ادامه و بین «آکولاد»ها قرار میگیرد.
بدنه میتواند از «ویژگی»های ذخیرهای تشکیل شده باشد که درست مانند ثابتها یا متغیرهای معمولی تعریف میشوند و رفتار میکنند.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id = makeRandomID()
}
همانطور که اشاره شد، تعریف یک struct یا class فقط تعریف یک «نوع» جدید است. این کار فقط نقشهای است برای اینکه مقادیر آن نوع چه شکلی خواهند داشت، اما در واقع هیچ مقداری از آن نوع نمیسازد که شما با آن کار کنید.
برای ساختن یک «نمونه» از آن نوع، باید اسم نوع را بنویسید و پس از آن یک جفت پرانتز بگذارید.
let someStats = CharacterStats()
let someCharacter = GameCharacter()
این کار مقادیری از این نوعها میسازد که ویژگیهایشان با مقدارهای پیشفرضِ دادهشده در تعریف پر میشوند. توجه کنید که در حالتهای اختیاری مانند ویژگی name در GameCharacter، اگر مقداری داده نشود، آن ویژگی بهطور پیشفرض nil میشود، درست مانند تعریف انواع اختیاری معمولی که در آنها مقدار بلافاصله داده نمیشود.
در مورد structها، Swift بهطور خودکار چیزی به نام «مقدارساز عضو به عضو» فراهم میکند که در آن میتوان مقادیر ویژگیهای struct را داخل پرانتزها داد؛ این مقادیر جای مقدارهای پیشفرض تعریف را میگیرند.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
میتوان با «نشانهگذاری نقطهای» به ویژگیهای struct و class دسترسی پیدا کرد؛ در این روش، پس از اسم مقدار یک نقطه (.) و بعد اسم ویژگی میآید. اگر یک ویژگی struct یا class خودش ویژگیهایی داشته باشد، با همین نشانهگذاری نقطهای میتوان به این ویژگیهای تودرتو هم دسترسی پیدا کرد.
این نشانهگذاری هم برای خواندن مقدار ویژگی به کار میرود و هم، در جایی که مجاز باشد، برای تغییر آن.
someStats.health
// => 0
someCharacter.name
// => nil
someStats.health = 87.3
someStats.health
// => 87.3
someCharacter.name = "Luther"
someCharacter.name
// => "Luther"
someCharacter.id = "new id"
// Error: Cannot assign to property: 'id' is a 'let' constant
ویژگیها داده را در structها و classهای شما نگه میدارند؛ در کنار آنها میتوانید «متد»هایی هم تعریف کنید که توابع را در struct یا class شما ذخیره میکنند.
متدها درست مانند یک تابع معمولی تعریف میشوند، فقط داخل بدنهی struct یا class. توجه کنید که اگر تابعی مقدار یک ویژگی را در یک struct تغییر دهد، باید پیش از آن کلیدواژهی mutating بیاید. بهعلاوه، اگر متدی بتواند یک ویژگی را تغییر دهد، آن ویژگی باید با var تعریف شود، نه let، درست مانند متغیرهای معمولی.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
mutating func takeHit(_ damage: Double) {
health = max(0.0, health - damage)
}
func canLift(_ weight: Int) -> Bool {
weight < strength * 100
}
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id: String = makeRandomID()
func takesDamage(_ damage: Double) {
stats.takeHit(damage)
if stats.health <= 0 {
active = false
}
}
func sayName() -> String {
return "My name is \(name ?? "no one"), my class is \(characterClass ?? "undetermined")"
}
func lift(_ weight: Int) -> String {
if stats.canLift(weight) {
return "No problem!"
} else {
return "Ooof! No way."
}
}
}
میتوان این متدها را درست مانند ویژگیها با نشانهگذاری نقطهای فراخوانی کرد.
var myChar = GameCharacter()
myChar.stats = CharacterStats(health: 72.8, speed: 19, strength: 6)
myChar.active = true
myChar.lift(750)
// => "Ooof! No way."
myChar.takesDamage(80)
myChar.active
// => false
selfهر نمونهی یک struct یا class یک ویژگی ضمنی به اسم self دارد که به خود آن نمونه اشاره میکند.
رایجترین کاربرد self رفع ابهام بین یک ویژگی نمونه و پارامتری است که اسم یکسانی دارند:
struct Position {
var x = 0
mutating func move(x: Int) {
// Here, 'self.x' refers to the struct's property,
// while 'x' refers to the parameter passed to the method.
self.x = x
}
}
انواع Swift یک کلیدواژهی Self با حرف بزرگ هم دارند که به نوعِ نمونه اشاره میکند، نه به خود نمونه.
در این تمرین، یک سیستم کامپیوتری مبتنی بر پنجره را شبیهسازی میکنید. چند پنجره میسازید که میتوانند جابهجا شوند، اندازهشان تغییر کند و محتویاتشان را نمایش دهند. تصویر زیر نمونهای از مقدارهایی است که در ادامه با آنها کار میکنید.
<--------------------- screenSize.width --------------------->
^ ╔════════════════════════════════════════════════════════════╗
| ║ ║
| ║ position.x,_ ║
| ║ position.y \ ║
| ║ \<----- size.width -----> ║
| ║ ^ *──────────────────────┐ ║
| ║ | │ title │ ║
| ║ | ├──────────────────────┤ ║
screenSize.height ║ | │ │ ║
| ║ size.height │ │ ║
| ║ | │ contents │ ║
| ║ | │ │ ║
| ║ | │ │ ║
| ║ v └──────────────────────┘ ║
| ║ ║
| ║ ║
v ╚════════════════════════════════════════════════════════════╝
Size
ساختاری به اسم Size تعریف کنید که دو ویژگی Int دارد، width و height، که به ترتیب عرض و ارتفاع فعلی پنجره را نگه میدارند.
عرض و ارتفاع اولیه باید به ترتیب 80 و 60 باشند.
متدی به اسم resize(newWidth:newHeight:) هم اضافه کنید که پارامترهای عرض و ارتفاع جدید را میگیرد و ویژگیها را طوری تغییر میدهد که اندازهی جدید را نشان دهند.
let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800
Position
ساختاری به اسم Position تعریف کنید که دو ویژگی Int دارد، x و y، که به ترتیب موقعیت افقی و عمودی فعلی گوشهی بالا-چپ پنجره را نگه میدارند.
مقدارهای اولیهی x و y هر کدام باید 0 باشد. موقعیت (0, 0) گوشهی بالا-چپ صفحه است؛ هر چه به سمت راست میروید مقدارهای x بزرگتر میشوند و هر چه به سمت پایین میروید مقدارهای y بزرگتر میشوند.
متدی به اسم moveTo(newX:newY:) هم اضافه کنید که پارامترهای x و y جدید را میگیرد و ویژگیها را طوری تغییر میدهد که موقعیت جدید را نشان دهند.
var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100
Window
یک کلاس پنجره تعریف کنید که ویژگیهای زیر را دارد:
title : String، مقدار اولیه "New Window" استscreenSize : Size، مقداری ثابت که width آن 800 و height آن 600 استsize : Size، مقدار اولیهاش مقدار پیشفرض ساختار Size استposition : Position، مقدار اولیهاش مقدار پیشفرض ساختار Position استcontents : String?، مقدار اولیهاش nil استبرای کلاس یک مقداردهندهی اولیهی خالی هم تعریف کنید.
let window = Window()
window.title
// Returns "New Window"
resize(to:) : (Size) -> () - این متد یک ساختار Size را به عنوان ورودی میگیرد و تلاش میکند اندازهی پنجره را به اندازهی مشخصشده تغییر دهد. با این حال، اندازهی جدید نمیتواند از مرزهای مشخصی فراتر برود.x = 400، y = 300 باشد و درخواست تغییر اندازه به height = 400، width = 300 داده شود، پنجره به height = 300، width = 300 تغییر اندازه داده میشود، چون صفحه در جهت y بهقدر کافی بزرگ نیست که این درخواست را کامل در خود جای دهد.move(to:) : (Position) -> () - این متد شبیه resize(to:) است، اما به جای اندازه، موقعیت پنجره را به مقدار درخواستی تنظیم میکند. مثل resize، موقعیت جدید هم نمیتواند از حدهای مشخصی فراتر برود.x و y مقدار 0 است.x = 250، y = 100 باشد و درخواست جابهجایی به x = 600، y = 200 داده شود، پنجره به x = 550، y = 200 جابهجا میشود، چون صفحه در جهت x بهقدر کافی بزرگ نیست که این درخواست را کامل در خود جای دهد.update(title:) : (String) -> () - این متد ویژگی title را به مقدار رشتهای که به آن پاس داده شده تنظیم میکند.update(text:) : (String?) -> () - این متد ویژگی contents را به مقدار رشتهی اختیاری که به آن پاس داده شده تنظیم میکند.display() : () -> String - این متد رشتهای برمیگرداند که وضعیت فعلی پنجره را توصیف میکند. برای نمونه، اگر title پنجره "My First Window" باشد و position آن x = 10، y = 100؛ size آن width = 200، height = 150؛ و contents آن "I 😍 my window" باشد، باید رشتهی زیر را برگرداند: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - اگر contents برابر nil باشد، آخرین خط باید "[This window intentionally left blank]" باشدWindow
سیستم پنجره باید یک مقداردهندهی اولیه داشته باشد تا بتوان پنجره را با ورودیهای سفارشی ساخت.
یک مقداردهندهی اولیهی دیگر برای کلاس Window بسازید.
این مقداردهندهی اولیه باید پارامترهای زیر را بگیرد: title، contents و دو پارامتر اختیاری size و position.
پارامترهای size و position باید بهطور پیشفرض برابر مقدارهای پیشفرض ساختارهای Size و Position باشند.
let window = Window(title: "My First Window", contents: "I 😍 my window")
window.display()
// Returns "My First Window\nPosition: (0, 0), Size: (80 x 60)\nI 😍 my window\n"
let window2 = Window(title: "My Second Window", contents: "I 😍 my window", size: Size(width: 200, height: 150), position: Position(x: 10, y: 100))
window2.display()
// Returns "My Second Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n"