Link Search Menu Expand Document

Методи

Методи – це функції, що асоційовані із певним типом. Як класи, так і структури й перечислення можуть визначати методи екземплярів, що інкапсулюють певні завдання та функціональність для роботи з екземпляром даного типу. Класи, структури та перечислення також можуть визначати методи типу, котрі асоціюються із самим типом. Методи типу є аналогічними до методів класу у мові Objective-C.

Можливість структур та перечислень визначати методи у Swift є значною відмінністю цієї мови від мов C та Objective-C. У Objective-C, класи є єдиними типами, що можуть визначати методи. У Swift можна обирати між класом, структурою та перечисленням, маючи при цьому гнучкість для визначення методів у типі, що ви створюєте.

Методи екземплярів

Метод екземпляру – це функція, що належить до екземпляра певного класу, структури чи перечислення. Методи екземплярів формують функціональність цих екземплярів, або надаючи способи доступу та модифікації властивостей екземпляру, або надаючи функціональність, що відноситься до призначення екземпляру. Методи екземплярів мають точно такий же синтаксис, що й функції, які описані в розділі Функції.

Методи екземплярів записуються поміж фігурних дужок типу, до якого вони належать. Метод екземпляру має доступ до всіх інших методів та властивостей цього типу. Метод екземпляру можна викликати лише на певному екземплярі типу, до якого він належить. Його не можна викликати окремо від існуючого екземпляру.

Ось приклад простого класу Counter, котрий можна використовувати для підрахунку кількості виникнень певної події:

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Клас Counter визначає три методи екземпляру:

  • increment() збільшує лічильник на 1.
  • increment(by: Int) збільшує лічильник на визначену цілочисельну величину.
  • reset() скидує лічильник до нуля.

Клас Counter також оголошує змінну властивість count для відслідковування поточного значення лічильника.

Методи екземпляру викликаються за допомогою синтаксису крапки, аналогічно до звернень до властивостей:

let counter = Counter()
// початкове значення лічильника 0
counter.increment()
// значення лічильника тепер 1
counter.increment(by: 5)
// значення лічильника тепер 6
counter.reset()
// значення лічильника тепер 0

Параметри функцій можуть мати як назву (для використання всередині тіла функції), так і мітку аргументу (для використання при виклику функції), як описано в підрозділі Мітки аргументів функцій та імена їх параметрів. Це ж саме працює і для параметрів методів, оскільки методи є просто функціями, що асоційовані з певним типом.

Властивість self

Кожен екземпляр типу має неявну властивість на ім’я self, котра завжди дорівнює самому екземпляру. Властивість self використовується для посилання на поточний екземпляр всередині його власних методів.

Метод increment() з прикладу вище може бути записаний наступним чином:

func increment() {
    self.count += 1
}

На практиці, писати self у коді потрібно не дуже часто. Якщо всередині певного методу не вказати явно self, Swift припускає, що відбувається посилання на властивість чи метод поточного екземпляра при будь-якому використанні відомої назви властивості чи методу. Це припущення демонструється використанням назви властивості count (замість self.count) всередині трьох методів класу Counter у прикладі вище.

Головним виключенням з цього правила є колізія імен: наприклад, коли ім’я параметру методу екземпляру співпадає з іменем властивості цього екземпляру. У цій ситуації, ім’я параметру має пріоритет, і стає потрібним звертатись до властивості більш точним способом. В подібних випадках властивість self використовується для розрізнення імені параметру та імені властивості.

Тут властивість self допомагає відрізнити параметр методу на ім’я x від властивості екземпляру, що теж називається x:

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("Ця точка знаходиться справа від лінії, де x == 1.0")
}
// Надрукує "Ця точка знаходиться справа від лінії, де x == 1.0"

Без префіксу self, компілятор Swift би припустив би, що обидва x посилаються на параметр методу на ім’я x.

Зміни типів-значень в методах екземплярів

Структури та перечислення є типами-значеннями. За замовчанням, властивості типів-значень не можуть бути змінені всередині методів екземпляру.

Однак, якщо потрібно змінити значення властивості структури чи перечислення всередині певного методу, це можна зробити шляхом увімкнення мутуючої поведінки для цього методу. Після цього метод може змінювати властивості екземпляру, і ці зміни записуються назад в оригінальну структуру після закінчення методу. Метод може навіть присвоїти повністю новий екземпляр неявній властивості self, і цей новий екземпляр замінить собою існуючий екземпляр по закінченню метода.

Мутуюча поведінка методу вмикається шляхом вказування ключового слова mutating перед ключовим словом mutating в оголошенні цього метода:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("Точка тепер має координати (\(somePoint.x), \(somePoint.y))")
// Надрукує "Точка тепер має координати (3.0, 4.0)"

Структура Point моделює точку на площині з координатами x та y; вона визначає мутуючий метод moveBy(x:y:), що пересовує точку на певну величину. Замість того, щоб повернути нову точку, цей метод фактично змінює саму точку, на якій він був викликаний. Ключове слово mutating додається до оголошення цього методу, щоб дозволити йому модифікувати властивості свого екземпляру.

Слід помітити, що не можна викликати мутуючий метод на константі типу-структури, оскільки її властивості не можна змінювати, навіть якщо вони й оголошені як змінні властивості. Це детально описано в підрозділі Властивості константних структур, що зберігаються. Наприклад:

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// тут буде повідомлення про помилку компіляції

Присвоєння self у мутуючому методі

У мутуючому методі можна присвоїти цілком новий екземпляр неявній властивості self. Приклад зі структурою Point вище можна переписати у наступний спосіб:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

У даній версії мутуючого методу moveBy(x:y:) створюється новий екземпляр структури зі значеннями x та y, що дорівнюють результату перенесення. Кінцевий результат виклику цієї альтернативної версії методу буде точно таким же, як і результат виклику попередньої версії.

Мутуючі методи перечислень можуть присвоїти неявному параметру self інший елемент цього ж перечислення:

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight тепер дорівнює .high
ovenLight.next()
// ovenLight тепер дорівнює .off

У даному прикладі визначено перечислення, що моделює вимикач із трьома станами. Вимикач циклічно змінює свій стан (поміж off, low та high) щоразу коли викликається його метод next().

Методи типів

Описані вище методи екземплярів є методами, що викликаються на екземплярі певного типу. Також можна визначати методи, що викликаються на самому типі. Такі методи називаються методами типів. Методи типу позначаються за допомогою ключового слова static, що записується перед ключовим словом func. У класах також можна використовувати для цього ключове слово class, що дозволить класам-нащадкам заміщувати реалізацію цього методу з батьківського класу.

Примітка

В Objective-C методи типів можна визначати лише для класів. У Swift методи типів можна визначати для класів, структур та перечислень. Кожен метод типу є явно прив’язаним до типу, що його підтримує.

Методи типів викликаються за допомогою синтаксису крапки, як і методи екземплярів. Однак, методи типу викликаються на типі, а не на екземплярі цього типу. Ось приклад того, як викликати метод типу на класі на ім’я SomeClass:

class SomeClass {
    class func someTypeMethod() {
        // тут йде реалізація методу
    }
}
SomeClass.someTypeMethod()

Неявна властивість self всередині тіла методу посилається на сам тип, а не на якийсь його екземпляр. Це дозволяє використовувати self, щоб відрізняти властивості типу від параметрів методу типу, так само як розрізняються властивості екземпляру від параметрів методу екземпляру.

Говорячи більш загально, будь-яка назва методу чи властивості, що використовується всередині тіла методу типу буде посилатись на інший метод чи властивість рівня типу. Метод типу може викликати інший метод цього ж типу просто за його іменем, без необхідності вказувати префікс з іменем типу. Аналогічно, метод типу структури чи перечислення може посилатись на властивості цього ж типу просто за їх назвою, без необхідності вказувати префікс з іменем типу.

В прикладі нижче оголошується структура на ім’я LevelTracker, що відслідковує прогрес гравця у різних рівнях чи сценах гри. Це гра на одного гравця, але можна зберігати інформацію про кількох гравців на одному пристрої.

Всі рівні гри (крім першого рівня) є заблокованими при першій грі. Щоразу, коли гравець закінчує рівень, цей рівень розблоковується для всіх гравців на даному пристрої. Структура LevelTracker використовує властивості та методи типу для того, щоб відслідковувати, які з рівнів гри вже було розблоковано. Вона також відслідковує поточний рівень кожного гравця.

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

Структура LevelTracker відслідковує найвищий рівень, що коли-небудь був розблокований будь-яким з гравців. Це значення зберігається у властивості типу на ім’я highestUnlockedLevel.

LevelTracker також визначає два методи типу для роботи із властивістю highestUnlockedLevel. Першим є метод типу на ім’я unlock(_:), котрий оновлює значення highestUnlockedLevel при кожному розблокуванні нового рівня. Другим є метод типу для зручності на ім’я isUnlocked(_:), котрий повертає true, якщо заданий в його аргументі рівень вже було розблоковано. Слід помітити, що в методах типу можна звертатись до властивості типу highestUnlockedLevel без потреби писати це як LevelTracker.highestUnlockedLevel.

Окрім методів та властивостей типу, структура LevelTracker містить метод та властивість екземпляру. Вони потрібні для відслідковування прогресу гри конкретного гравця. Властивість екземпляру currentLevel зберігає поточний рівень даного гравця.

Щоб полегшити керування властивістю currentLevel, структура LevelTracker визначає метод екземпляру на ім’я advance(to:), що пробує перевести гравця на заданий рівень. Спершу цей метод перевіряє, чи є заданий рівень уже розблокованим, і якщо це так – оновлює значення currentLevel. Даний метод повертає булеве значення, котре вказує на те, чи зміг даний гравець перейти на вказаний рівень. Оскільки ігнорування значення, що повертає цей метод, не обов’язково є помилкою, даний метод позначено атрибутом @discardableResult. Детальніше з цим атрибутом можна ознайомитись у розділі Атрибути.

Структура LevelTracker використовується нижче разом із класом Player, щоб відслідковувати та оновлювати прогрес конкретного гравця:

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

У класі Player створюється новий екземпляр структури LevelTracker для відслідковування прогресу гравця. У цьому класі також є метод complete(level:), що викликається при кожному завершенні рівня гравцем. Цей метод розблоковує наступний рівень для всіх гравців та оновлює прогрес гравця, переводячи його на наступний рівень. Булеве значення, що повертається методом advance(to:) ігнорується, бо точно відомо, що рівень було розблоковано внаслідок виклику методу типу LevelTracker.unlock(_:) у рядку вище.

Тепер можна створити екземпляр класу Player для нового гравця, і подивитись, що станеться, коли гравець завершить перший рівень:

var player = Player(name: "Майкл")
player.complete(level: 1)
print("Найвищий розблокований рівень тепер \(LevelTracker.highestUnlockedLevel)")
// Надрукує "Найвищий розблокований рівень тепер 2"

Якщо створити другого гравця, і спробувати перемістити його на рівень, що не був розблокований жодним з гравців у грі, то ця спроба переходу буде невдалою:

player = Player(name: "Щур")
if player.tracker.advance(to: 6) {
    print("Гравець тепер на рівні 6")
} else {
    print("Рівень 6 ще не розблокований")
}
// Надрукує "Рівень 6 ще не розблокованийи"