Свойства и методы типа
Вернёмся к бланкам. На каждом заполненном бланке Size свои числа: на одном ширина 2, на другом 5. Но шапка у всех бланков этой формы одна и та же. В ней напечатаны образец заполнения, квадрат 1 на 1, и подсказка, как быстро заполнить бланк для квадрата любого размера.
Шапка принадлежит самой форме, а не отдельному листу. Чтобы прочитать образец, не нужно заполнять ни одного бланка: он одинаков для всех.
Зачем такое в программе? Пусть программе часто нужен размер 1 на 1. Его можно объявить отдельной константой в главной части программы, но тогда он лежит вдали от Size, и искать его придётся по всей программе.
Удобнее держать такие данные внутри типа, рядом с его свойствами. Для этого в Swift есть слово static:
struct Size {
var width: Int
var height: Int
static let unit = Size(width: 1, height: 1)
static func square(_ side: Int) -> Size {
Size(width: side, height: side)
}
}
let big = Size.square(5)
print(Size.unit.width, big.width) // 1 5Давайте разберём программу по шагам:
widthиheight— обычные свойства: у каждого экземпляра свои.static let unit— образец из шапки: экземплярSizeс шириной и высотой1. Он хранится в самом типе, а не в экземплярах.static func square— подсказка из шапки: метод получает сторону и возвращает новый экземпляр, квадрат.Size.square(5)вызывает метод через имя типа. Экземпляр для этого не нужен, а результат попадает вbig.print(Size.unit.width, big.width)печатает1 5.
Свойство типа
static let unit — это свойство типа: оно принадлежит самому типу Size, а не экземпляру. Обычное свойство у каждого экземпляра своё, а свойство типа одно на всю программу.
Свойство типа читают через имя типа: Size.unit. В записи Size.unit.width две точки:
- первая ведёт от типа к свойству типа:
Size.unit— это экземпляр-образец; - вторая ведёт от этого экземпляра к его обычному свойству
width.
Вы уже встречали свойство типа в теме об операторах: Int.max — наибольшее значение типа Int. Его тоже читают через имя типа, и никакое число для этого не нужно.
Здесь картинка с шапкой неточна. Шапку видно на каждом бланке, а у экземпляра свойства типа нет: оно хранится в одном месте, в самом типе.
Только static let
Свойства типа в курсе объявляют через static let. Хранимое static var в Swift 6 не компилируется, даже если программа его не меняет. Строка static var total = 0 внутри структуры даёт ошибку static property 'total' is not concurrency-safe because it is nonisolated global shared mutable state. Компилятор сам советует заменить var на let. Причина та же, по которой функция не меняет переменную var из главной части программы: такое значение могли бы менять сразу несколько частей программы.
Метод типа
static func square — это метод типа. Его объявляют внутри типа, как обычный метод, только со словом static, а вызывают через имя типа: Size.square(5).
Метод типа вызывают без экземпляра, поэтому свойств какого-то одного экземпляра он не видит. Если написать в его теле width, компилятор ответит instance member 'width' cannot be used on type 'Size'. Всё нужное метод получает параметрами, как square получает side.
Часто метод типа строит готовый экземпляр. Вызов Size.square(5) читается почти как фраза: «размер — квадрат со стороной 5».
Об этом часто спрашивают на собеседовании: чем свойство или метод типа отличается от обычного? Ответ состоит из двух частей:
- обычное свойство у каждого экземпляра своё, а свойство типа одно на всю программу;
- к обычным свойствам и методам обращаются через экземпляр,
big.width, а к свойствам и методам типа — через имя типа,Size.unit.
Частая ошибка
Свойство типа прочитали через экземпляр:
print(big.unit.width) // ✗error: static member 'unit' cannot be used on instance of type 'Size'Программа не компилируется. У экземпляра big есть только width и height, а unit хранится в самом типе Size. Об этом и говорит компилятор: static-свойство нельзя использовать у экземпляра типа Size. Прочитайте его через имя типа: Size.unit.width.
Главное
- Свойство типа одно на всю программу и хранится в самом типе; его читают через имя типа:
Size.unit. - Метод типа объявляют через
static funcи вызывают через имя типа, без экземпляра:Size.square(5). - Через экземпляр свойство типа не прочитать:
big.unitне компилируется. - Хранимое свойство типа объявляют через
static let:static varв Swift 6 не компилируется.