swift

Классы в Swift

Введение

Классы — это одна из основ объектно-ориентированного программирования в 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)

Мы лишь начинает разговор о классах.