Книга: Swift. Основы разработки приложений под iOS, iPadOS и macOS. 5-е изд. дополненное и переработанное
Назад: 23. Структуры
Дальше: 25. Свойства

24. Классы

Классы являются наиболее функциональными конструкциями в разработке приложений. Данная глава призвана познакомить вас с их замечательными возможностями. Если вы ранее разрабатывали приложения на других языках программирования, то, возможно, уже осведомлены о классах. В этом случае имеющийся опыт пригодится вам при их изучении в Swift.

Классы очень похожи на структуры, но их отличают несколько ключевых моментов.

Тип. Класс — это тип-ссылка. Экземпляры класса передаются по ссылке, а не копированием.

Изменяемость. Экземпляр класса может изменять значения своих свойств, объявленных как переменная (var), даже если данный экземпляр хранится в константе (let). При этом использовать ключевое слово mutating для методов не требуется.

Наследование. Два класса могут быть в отношении «суперкласс–субкласс» друг к другу. Но обратите внимание: субкласс наследует и включает в себя все характеристики (свойства и методы) суперкласса и может быть дополнительно расширен. Об ограничениях, связанных с наследованием, рассказано в главе 27.

Инициализаторы. Класс имеет только пустой встроенный инициа­лизатор init(){}, который не требует передачи значения входных параметров для их установки в свойства.

Деинициализаторы. Swift позволяет создать деинициализатор — специальный метод, который автоматически вызывается при удалении экземпляра класса.

Приведение типов. В процессе выполнения программы вы можете проверить экземпляр класса на соответствие определенному типу данных.

Каждая из особенностей детально разбирается в книге.

Со временем вы научитесь определять наиболее подходящие конструкции для реализации используемых в программах сущностей: последовательность, коллекция, структура, класс, кортеж или что-то другое.

24.1. Синтаксис классов

Объявление классов очень похоже на объявление структур.

Синтаксис

class ИмяКласса {

        // свойства и методы класса

}

• Классы объявляются с помощью ключевого слова class, за которым следует имя создаваемого класса. Имя класса должно быть написано в верхнем верблюжьем регистре.

• Тело класса заключается в фигурные скобки и может содержать методы и свойства, а также другие элементы, с которыми мы еще не знакомы.

ПРИМЕЧАНИЕ При объявлении нового класса, как и при объявлении перечисления или структуры, создается новый тип данных, который может быть использован.

23.2. Свойства классов

Перейдем к практической стороне изучения классов. Класс, как и структура, может предоставлять механизмы для описания некоторой сущности. Эта сущность обычно обладает рядом характеристик, выраженных в классе в виде свойств класса. Для свойств могут быть указаны значения по умолчанию.

Как отмечалось ранее, класс имеет один встроенный инициализатор, который является пустым. Если у структуры инициализатор генерируется автоматически вместе с изменением состава ее свойств, то у класса для установки значений свойств требуется разрабатывать инциализаторы самостоятельно.

При создании экземпляра класса каждому свойству должно быть проинициализировано значение: либо через значения по умолчанию, либо в теле инициализатора.

При выполнении заданий в предыдущих главах мы описывали сущность «шахматная фигура» вначале с помощью перечисления Chessmen, а потом с помощью структуры Chessmen. Использование класса для моделирования шахматной фигуры предпочтительнее в первую очередь в связи с тем, что каждая отдельная фигура — это уникальный объект со своими характеристиками. Его модификация в программе с использованием ссылок (а класс — это тип-ссылка) значительно упростит работу.

Рассмотрим пример использования классов.

Необходимо смоделировать сущность «шахматная фигура». При этом она должна обладать следующим набором свойств:

• тип фигуры;

• цвет фигуры;

• координаты на игровом поле.

В еще одном дополнительном свойстве мы будем хранить символ, соответствующий шахматной фигуре (в Unicode входят необходимые символы).

Координаты будут служить не только для того, чтобы определить местоположение фигуры на шахматной доске, но и для определения факта ее присутствия. Если фигура убита или еще не выставлена, то значение координат будет nil.

Так как у класса будут определены свойства, необходимо разработать инициализатор, который будет устанавливать их значения.

В листинге 24.1 приведен код класса, описывающий сущность «шахматная фигура».

Листинг 24.1

class Chessman {

    // тип фигуры

    let type: String

    // цвет фигуры

    let color: String

    //координаты фигуры

    var coordinates: (String, Int)? = nil

    // символ, соответствующий фигуре

    let figureSymbol: Character

    // инициализатор

    init(type: String, color: String, figure: Character){

        self.type = type

        self.color = color

        self.figureSymbol = figure

    }

}

// создаем экземпляр фигуры

var kingWhite = Chessman(type: "king", color: "white", figure:

                "\u{2654}")

ПРИМЕЧАНИЕ Коды Unicode-символов, соответствующих шахматным фигурам, вы можете самостоятельно посмотреть в интернете.

Каждая из характеристик фигуры выражена в отдельном свойстве класса. Тип данных свойства coordinate является опциональным кортежем. Это связано с тем, что фигура может быть убрана с игрового поля (тогда свойство будет nil). Координаты фигуры на шахматном поле задаются с помощью строки и числа.

В разработанном инициализаторе указаны входные аргументы, значения которых используются в качестве значений свойств экземпляра.

В результате выполнения кода в переменной kingWhite находится экземпляр класса Chessman, описывающий фигуру «Белый король». Фигура еще не имеет координат, а значит, не выставлена на игровое поле (ее координаты равны nil).

Свойства type и color могут принять значения из четко определенного перечня, поэтому имеет смысл реализовать два перечисления: одно должно содержать типы шахматных фигур, второе цвета (лис­тинг 24.2).

Листинг 24.2

// тип шахматной фигуры

enum ChessmanType {

    case king, castle, bishop, pawn, knight, queen

}

// цвета фигур

enum ChessmanColor {

    case black, white

}

Созданные перечисления должны найти место в качестве типов соответствующих свойств класса Chessman. Не забывайте, что и входные аргументы инициализатора должны измениться соответствующим образом (листинг 24.2а).

Листинг 24.2а

class Chessman {

    let type: ChessmanType

    let color: ChessmanColor

    var coordinates: (String, Int)? = nil

    let figureSymbol: Character

    init(type: ChessmanType, color: ChessmanColor, figure:Character){

        self.type = type

        self.color = color

        self.figureSymbol = figure

    }

}

var kingWhite = Chessman(type: .king, color: .white, figure:

                "\u{2654}")

Теперь при создании модели шахматной фигуры необходимо передавать значения типов ChessmanType и ChessmanColor вместо String.

Созданные дополнительные связи обеспечивают корректность ввода данных при создании экземпляра класса.

24.3. Методы классов

Сущность «Шахматная фигура» уже является вполне рабочей. На ее основе можно описать любую фигуру. Тем не менее описанная фигура все еще является «мертвой» в том смысле, что она не может быть использована непосредственно для игры. Это связано с тем, что еще не разработаны механизмы, позволяющие установить фигуру на игровое поле и динамически ее перемещать.

Классы, как и структуры с перечислениями, могут содержать произвольные методы, обеспечивающие функциональную нагрузку класса. Не забывайте, что в классах нет необходимости использовать ключевое слово mutating для методов, меняющих значения свойств.

Немного оживим созданную модель шахматной фигуры, создав несколько методов (листинг 24.3):

• установка координат фигуры (при выставлении на поле или при движении);

• снятие фигуры с игрового поля (окончание партии или гибель фигуры).

Листинг 24.3

class Chessman {

    let type: ChessmanType

    let color: ChessmanColor

    var coordinates: (String, Int)? = nil

    let figureSymbol: Character

    init(type: ChessmanType, color: ChessmanColor, figure:Character){

        self.type = type

        self.color = color

        self.figureSymbol = figure

    }

    // метод установки координат

    func setCoordinates(char c:String, num n: Int){

        self.coordinates = (c, n)

    }

    // метод, убивающий фигуру

    func kill(){

        self.coordinates = nil

    }

}

var kingWhite = Chessman(type: .king, color: .white, figure:

                "\u{2654}")

kingWhite.setCoordinates(char: "E", num: 1)

В результате фигура «Белый король» выставляется в позицию с координатами E1.

На самом деле для действительного размещения фигуры на игровом поле необходимо смоделировать саму шахматную доску. И этим вопросом мы займемся уже в скором времени.

24.4. Инициализаторы классов

Класс может содержать произвольное количество разработанных инициализаторов, различающихся лишь набором входных аргументов. Это никоим образом не влияет на работу самого класса, а лишь дает нам более широкие возможности при создании его экземпляров.

Рассмотрим процесс создания дополнительного инициализатора. Существующий класс Chessman не позволяет одним выражением создать фигуру и выставить ее на поле. Сейчас для этого используются два независимых выражения. Давайте разработаем второй инициализатор, который будет дополнительно принимать координаты фигуры (листинг 24.4).

Листинг 24.4

class Chessman {

    let type: ChessmanType

    let color: ChessmanColor

    var coordinates: (String, Int)? = nil

    let figureSymbol: Character

    init(type: ChessmanType, color: ChessmanColor, figure:Character){

        self.type = type

        self.color = color

        self.figureSymbol = figure

    }

    init(type: ChessmanType, color: ChessmanColor, figure: Character,

               coordinates: (String, Int)){

        self.type = type

        self.color = color

        self.figureSymbol = figure

        self.setCoordinates(char: coordinates.0, num: coordinates.1)

    }

    func setCoordinates(char c:String, num n: Int){

        self.coordinates = (c, n)

    }

    func kill(){

        self.coordinates = nil

    }

}

var queenBlack = Chessman(type: .queen, color: .black, figure:

                 "\u{2655}", coordinates: ("A", 6))

Так как код установки координат уже написан в методе setCoordi­nates(char:num:), то, во избежание дублирования, в инициализаторе этот метод просто вызывается.

При объявлении нового экземпляра в окне автодополнения будут предлагаться на выбор два инициализатора, объявленные в классе Chessman.

24.5. Вложенные в класс типы

Очень часто перечисления, структуры и классы создаются для того, чтобы расширить функциональность и удобство использования определенного типа данных. Такой подход мы встречали, когда разрабатывали перечисления ChessmanColor и ChessmanType, использующиеся в классе Chessman. В данном случае перечисления нужны исключительно в контексте класса, описывающего шахматную фигуру, и нигде больше.

В такой ситуации вы можете вложить перечисления в класс, то есть описать их не глобально, а в пределах тела класса (листинг 24.5).

Листинг 24.5

class Chessman {

    enum ChessmanType {

        case king, castle, bishop, pawn, knight, queen

    }

    enum ChessmanColor {

        case black, white

    }

    let type: ChessmanType

    let color: ChessmanColor

    var coordinates: (String, Int)? = nil

    let figureSymbol: Character

    init(type: ChessmanType, color: ChessmanColor, figure:

        Character){

        self.type = type

        self.color = color

        self.figureSymbol = figure

    }

    init(type: ChessmanType, color: ChessmanColor, figure:

        Character, coordinates: (String, Int)){

        self.type = type

        self.color = color

        self.figureSymbol = figure

        self.setCoordinates(char: coordinates.0, num:coordinates.1)

    }

    func setCoordinates(char c:String, num n: Int){

        self.coordinates = (c, n)

    }

    func kill(){

        self.coordinates = nil

    }

}

Структуры ChessmanColor и ChessmanType теперь являются вложенными в класс Chessman и существуют только в пределах области видимости данного класса. Мы уже неоднократно видели с вами подобный подход.

Ссылки на вложенные типы

В некоторых ситуациях может возникнуть необходимость использовать вложенные типы вне определяющего их контекста. Для этого необходимо указать имя родительского типа, после которого должно следовать имя требуемого типа данных (листинг 24.6). В этом примере мы получаем доступ к одному из членов перечисления ChessmanType, объявленного в контексте класса Chessman.

Листинг 24.6

var linkToEnumType = Chessman.ChessmanType.bishop

Назад: 23. Структуры
Дальше: 25. Свойства