Структури та класи - це два з основних будівельних блоків програмування мовою Swift. Обидва вони дають змогу обʼєднувати повʼязані дані та функції в самодостатні одиниці функціональності. І коли ми визначаємо структуру чи клас, ми визначаємо новий тип, який можна використовувати у Swift, так само як ми користувалися типами, з якими вже працювали, наприклад Int і String.
У структур і класів у Swift багато спільного. Серед іншого, і ті, й інші вміють зберігати значення у властивостях і надавати функціональність через методи.
Кожен із них надає ще й додаткову функціональність, яка виходить за межі цієї вправи.
І структури, і класи визначають приблизно однаково. Спершу йде відповідне ключове слово, потім назва типу, який ми визначаємо, а далі тіло структури чи класу, вміщене у фігурні дужки.
Тіло може складатися зі збережених властивостей, які визначають і які поводяться точно так само, як звичайні константи чи змінні.
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()
}
Як зазначено вище, визначення структури чи класу - це лише визначення нового типу. Це лише креслення того, який вигляд матимуть значення цього типу, але воно не створює жодних значень цього типу, з якими можна було б працювати.
Щоб створити екземпляр цього типу, треба написати назву типу, а потім пару дужок.
let someStats = CharacterStats()
let someCharacter = GameCharacter()
Так ми створимо значення цих типів, де властивості заповнені типовими значеннями, заданими у визначенні. Зауважмо, що в опціональних випадках, як-от властивість name у GameCharacter, властивість типово матиме значення nil, якщо їй не надати значення, точнісінько як зі звичайними опціональними типами, де значення не задають одразу.
Для структур Swift автоматично надає те, що називають почленним ініціалізатором: значення для властивостей структури можна передати в дужках, і вони перекриють типові значення з визначення.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
До властивостей структур і класів можна звертатися через точкову нотацію, де після назви значення йде крапка (.) і назва властивості. Якщо у властивості структури чи класу є власні властивості, цією самою точковою нотацією можна дістатися і до вкладених властивостей.
Цю нотацію можна використовувати і щоб отримати значення властивості, і, де це дозволено, щоб його змінити.
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
Окрім властивостей, які зберігають дані в структурах і класах, можна також визначати методи, які зберігають функції в структурі чи класі.
Методи визначають так само, як звичайні функції, тільки всередині тіла структури чи класу. Зауважмо: якщо функція змінює значення властивості в структурі, перед нею обовʼязково має стояти ключове слово 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, яка вказує на сам цей екземпляр.
Найчастіше 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 з двома властивостями типу 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 з двома властивостями типу 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
Визначте клас вікна з такими властивостями:
title : String, початкове значення - "New Window"screenSize : Size, стале значення з width = 800 і height = 600size : 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.x = 250, y = 100, а запит на переміщення вказує x = 600, y = 200, то вікно буде переміщено до x = 550, y = 200, оскільки екран недостатньо великий у напрямку x, щоб повністю задовольнити запит.update(title:) : (String) -> (). Цей метод встановлює властивість title у значення рядка тексту (англ. string), який було передано.update(text:) : (String?) -> (). Цей метод встановлює властивість contents у значення необовʼязкового рядка тексту, який було передано.display() : () -> String. Цей метод повертає рядок тексту, який описує поточний стан вікна. Наприклад, якщо вікно має title "My First Window", позицію: x = 10, y = 100; розмір: width = 200, height = 150; і вміст: "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.
Ініціалізатор має приймати такі параметри: 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"
Зареєструйтеся на Exercism, щоб вивчати й опановувати Swift, а також 35 концепцій116 вправ та справжнє наставництво від людей, і все це безкоштовно.