Методы
Методы - это функции, которые связаны с определенным типом. Классы, структуры и перечисления - все они могут определять методы экземпляра, которые включают в себя определенные задачи и функциональность для работы с экземпляром данного типа. Классы, структуры и перечисления так же могут определить методы типа, которые связаны с самим типом. Методы типа работают аналогично методам класса в 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) увеличивает значение счетчика на определенное значение amount.
- reset() сбрасывает значение счетчика на 0.
Класс Counter так же определяет переменное свойство count, для отслеживания значения счетчика.
Вы можете вызвать методы экземпляра с тем же точечным синтаксисом:
let counter = Counter()
// начальное значение counter равно 0
counter.increment()
// теперь значение counter равно 1
counter.increment(by: 5)
// теперь значение counter равно 6
counter.reset()
// теперь значение counter равно 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 перед словом func для определения метода:
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 определяет метод moveBy(x:y:), который передвигает точку типа Point на определенное количество значений. Вместо того, чтобы вернуть новую точку, этот метод фактически изменяет координаты точки, которая его вызвала. Ключевое слово mutating добавлено к определению метода, для того, чтобы изменить значения свойств.
Обратите внимание, что вы не можете вызвать изменяющий (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, этот метод проверяет доступен ли запрашиваемый новый уровень. Метод advance(to:) возвращает логическое значение, указывающее, удалось ли ему поставить currentLevel. Не обязательно должно быть ошибкой игнорирование результата работы функции advance(to:), поэтому этот метод имеет маркировку @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: "Argyrios")
player.complete(level: 1)
print("Самый последний доступный уровень сейчас равен \(LevelTracker.highestUnlockedLevel)")
//Выведет "Самый последний доступный уровень сейчас равен 2"
Если вы создадите второго игрока, и попробуете им начать прохождение уровня, который не был разблокирован ни одним игроком в игре, то вы увидите, что эта попытка будет неудачной:
player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
print("Игрок на уровне 6")
} else {
print("Уровень 6 еще не разблокирован")
}
// Выведет "Уровень 6 еще не разблокирован"