Перейти к содержимому
Теория · Классы · урок 8 из 10

Проверка и приведение типа

Представьте приют для животных. В его журнале все записаны одинаково — как животные. Чтобы позвать кого-то поиграть с мячом, сначала нужно выяснить, кто из записанных — собака.

В программе такой журнал — массив родительского класса. В уроке о наследовании константа типа Animal хранила экземпляр подкласса. Массив [Animal] тоже может хранить и собак, и кошек:

SWIFT
class Animal {
    var name: String
    init(name: String) {
        self.name = name
    }
}
class Dog: Animal {
    func fetch() -> String { "\(name) brings the ball" }
}
class Cat: Animal {}

let shelter: [Animal] = [Dog(name: "Rex"), Cat(name: "Tom")]
print(shelter[0].name, shelter[1].name) // Rex Tom

Давайте разберём программу:

  1. Dog добавляет к Animal метод fetch(), а Cat не добавляет ничего — у него пустые фигурные скобки.
  2. shelter — массив типа [Animal], и в нём лежат экземпляры двух разных подклассов.
  3. Свойство name есть у любого Animal, поэтому shelter[0].name и shelter[1].name читаются без всяких проверок.

А вот fetch() у Animal нет. Строка print(shelter[0].fetch()) не скомпилируется, хотя в shelter[0] лежит собака:

TEXT
error: value of type 'Animal' has no member 'fetch'

Компилятор знает только тип массива — [Animal]. Что лежит в нём на самом деле, выясняется во время работы программы, и для этого есть проверка и приведение типа.

Проверка типа: is

Оператор is проверяет, относится ли экземпляр к нужному классу, и отвечает true или false:

SWIFT
var dogs = 0
for pet in shelter {
    if pet is Dog {
        dogs += 1
    }
}
print(dogs) // 1

Цикл проходит по приюту и считает собак. pet is Dog верно для Rex и неверно для Tom, поэтому dogs равно 1. Экземпляр подкласса Dog тоже прошёл бы эту проверку: щенок — тоже собака.

Приведение типа: as? и as!

Узнать, что перед нами собака, мало: чтобы вызвать fetch(), нужна константа типа Dog. Приведение типа — попытка посмотреть на значение как на значение другого типа, например на Animal как на Dog. Это не преобразование вроде Double(count): приведение не создаёт новое значение и работает с тем же экземпляром. Для приведения в Swift есть два оператора.

as?

as? пробует привести тип и возвращает опционал: экземпляр нужного класса или nil, если класс другой. Его извлекают через if let, как любой опционал:

SWIFT
for pet in shelter {
    if let dog = pet as? Dog {
        print(dog.fetch()) // Rex brings the ball
    }
}

Для Rex приведение удаётся, и dog — константа типа Dog с методом fetch(). Для Tom выражение pet as? Dog даёт nil, и тело if пропускается.

as!

as! приводит тип без проверки и возвращает не опционал, а сразу значение нужного типа. Он похож на принудительное извлечение !: если класс не тот, программа аварийно завершится. shelter[0] as! Dog сработает, потому что там собака, а с кошкой выйдет ошибка — о ней ниже.

Здесь картинка с приютом неточна. Кошку в журнале можно по ошибке записать собакой, а приведение типа ничего не переписывает: экземпляр Cat остаётся кошкой, и as? только проверяет, кто перед нами.

На собеседовании спрашивают, чем as? отличается от as!. Ответ: as? возвращает опционал и при неудаче даёт nil, а as! при неудаче аварийно завершает программу.

Частая ошибка

as! применили там, где класс не гарантирован:

SWIFT
let tom = shelter[1] as! Dog // ✗

Программа аварийно завершается: в shelter[1] лежит кошка, и привести её к Dog нельзя. В сообщении Swift пишет, что не может привести значение типа main.Cat к main.Dog. Перед именем класса стоит имя программы: после swiftc main.swift это main. Числа в скобках рядом с именами при каждом запуске свои. Если класс может оказаться другим, используйте as? с if let.

Главное

  • is проверяет класс экземпляра и отвечает true или false; экземпляр подкласса тоже проходит проверку.
  • as? приводит тип и возвращает опционал: значение нужного класса или nil.
  • as! приводит тип без проверки и аварийно завершает программу, если класс не тот.
  • Компилятор знает только объявленный тип: чтобы вызвать метод подкласса у элемента [Animal], элемент сначала приводят через as?.