Классы — это одна из основ объектно-ориентированного программирования в Swift.
Они позволяют создавать сложные структуры данных, объединяя свойства и методы в одной сущности.
В этой статье мы кратко познакомимся с ключевыми аспектами классов.
Объявить класс довольно просто, достаточно ключевого слова class
class ИмяКласса {
// свойства и методы класса
}
class Person {
var name: String
var origin: String
// Инициализатор
init(name: String, origin String) {
self.name = name
self.origin = origin
}
func introduce() {
print("Привет, меня зовут \(name) и я из \(origin)")
}
}
Ключевую роль в создании класса играет инициализатор.
Когда мы говорили про структуры – не уделили ему должного внимания по той причине, что в структурах он опционален, но в классах – он всегда необходим, если есть хотя бы одно поле, без значения по умолчанию.
let person = Person(name: "Pier Dunn", origin: "France")
person.introduce() // Привет, меня зовут Pier Dunn и я из France
Как вы могли заметить, конкретно в данном случае – инициализатор прост, мы лишь спрашиваем значение для двух неизвестных полей (name и age).
Более подробно об инициализации мы поговорим в следующей статье.
class Car {
var brand: String // хранимое свойство (Может быть изменён)
let year: Int // константное свойство
var mileage: Double = 0.0 // свойство с начальным значением (По умолчанию)
init(brand: String, year: Int) {
self.brand = brand
self.year = year
}
}
В данном случае в инициализаторе мы не спрашиваем о mileage так как, мы прописали ему значение по умолчанию.
Тем не менее ничто нам не мешает добавить значение и для этого поля в инициализатор.
Разумеется, класс может содержать внутри себя функции, можно даже сказать, что это его основная роль.
Важно понимать – как поля так и функции должны быть объединены по смыслу в определённый класс и представлять из себя определённую кодовую единицу.
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
Наследование – важное понятие в программировании.
Представьте, что у вас есть базовый класс, которые имеет в себе множество полей и функций, которые определяют логику его работы, но вместе с тем – вам необходимы ещё и конкретные классы, поведение, которых очень похоже на базовый класс, но имеет небольшие отличия.
В таком случае – мы можем вынести основную логику в базовый класс, а уже в конкретных будем её редактировать, если это понадобится.
В мире есть много животных, но все кроме котов практически бесполезны и уродливы. В свою очередь коты очень красивы и грациозны, а так же каждый их миллиметр создан ради одной цели – безжалостно убивать.
Если перед нами стоит задача написать отдельный класс для каждого из породы кошачьих – нам нет смысла дублировать код, мы создадим базовый класс (Cat) а уже в конкретных (HomeCat, Lion) отрегулируем логику.
class Cat {
/// Кличка
let name: String
/// Размер хвоста
let tailLenght: Int
init(name: String, tailLenght: Int) {
self.name = name
self.tailLenght = tailLenght
}
/// Издать звук
func makeSound() {
print("Мяу мяу мяу")
}
/// Бежать
func run() {
print("Быстро бегу")
}
}
Это наш базовый класс Cat – у каждой кошки есть хвост, она может издавать звук и бегать.
А теперь создадим домашнюю кошку и скажем ей унаследоваться от базовой кошки
class HomeCat: Cat {
var hasEggs: Bool
init(name: String, tailLenght: Int, hasEggs: Bool) {
self.hasEggs = hasEggs
super.init(name: name, tailLenght: tailLenght)
}
}
Теперь она имеет внутри себя всю логику базового класса Cat и конкретную логику для домашней кошки.
Для домашней кошки мы добавили поле hasEggs (Кастрирован ли ?) очевидно, что подобное поле имеет смысл только для домашней кошки, точнее – кота.
Вряд ли вам придёт в голову кастрировать льва.
class Lion: Cat {
/// Издать звук
override func makeSound() {
print("РРрррррррррр")
}
}
Для льва мы решили, что базовая функция makeSound (она содержится в Cat) точно не должна делать "Мяу мяу" – льву нужно Рррычать, поэтому мы переопределили override данную функцию и теперь любой объект с типом данных Lion будет не мяукать, а рычать.
А тот кто его не переопределил –продолжит мяукать
Представим, что у нас есть класс MathOperations а в нём лишь одна функция, которая возводит значение в квадрат.
class MathOperations {
var value: Double
init(value: Double) {
self.value = value
}
func square() -> Double {
return value * value
}
}
Используя ключевое слово extension мы можем расширить возможности класса и добавить ему ещё функций, но сделать это не внутри класса, а где угодно.
Это довольно просто и бывает полезно.
extension MathOperations {
func square() -> Double {
return value * value
}
func cube() -> Double {
return value * value * value
}
}
extension MathOperations: CustomStringConvertible {
var description: String {
return "MathOperations with value: \(value)"
}
}
Обратите внимание – мы можем добавить только функции и вычисляемые свойства, но никак не обычные поля!
extension MathOperations {
/// Вот так сделать не получится
let pi: Double
}
Когда мы знакомились с массивами, мы говорили, о том, что массив может хранить данные только одного типа.
Но ничто нам не мешает выделить базовый класс MediaItem и унаследовать от него Movie и Song, что бы у нас получился массив let library: [MediaItem] в котором, мы могли бы хранить все наши классы, которые унаследуются от MediaItem
class MediaItem {
var name: String
init(name: String) { self.name = name }
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library: [MediaItem] = [
Movie(name: "Начало", director: "Кристофер Нолан"),
Song(name: "Bohemian Rhapsody", artist: "Queen"),
Movie(name: "Матрица", director: "Лана Вачовски")
]
for item in library {
if let movie = item as? Movie {
print("Фильм: \(movie.name), режиссер: \(movie.director)")
} else if let song = item as? Song {
print("Песня: \(song.name), исполнитель: \(song.artist)")
}
}
Надеюсь вы помните – чтобы из базового класса MediaItem сделать конкретный класс Movie или Song мы должны использовать приведение типов (if let movie = item as? Movie)
Классы в Swift предоставляют мощный механизм для объектно-ориентированного программирования.
Они поддерживают наследование, полиморфизм, инкапсуляцию и автоматическое управление памятью через ARC.
Структуры ничего из этого списка не поддерживают! – поэтому, если не знаете, что выбрать – выбирайте struct, и лишь когда столкнётесь с нехваткой возможностей – переходите на class (Для этого достаточно сменить ключевое слово struct наclass)
Мы лишь начинает разговор о классах.