Проверка и приведение типа
Представьте приют для животных. В его журнале все записаны одинаково — как животные. Чтобы позвать кого-то поиграть с мячом, сначала нужно выяснить, кто из записанных — собака.
В программе такой журнал — массив родительского класса. В уроке о наследовании константа типа Animal хранила экземпляр подкласса. Массив [Animal] тоже может хранить и собак, и кошек:
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Давайте разберём программу:
Dogдобавляет кAnimalметодfetch(), аCatне добавляет ничего — у него пустые фигурные скобки.shelter— массив типа[Animal], и в нём лежат экземпляры двух разных подклассов.- Свойство
nameесть у любогоAnimal, поэтомуshelter[0].nameиshelter[1].nameчитаются без всяких проверок.
А вот fetch() у Animal нет. Строка print(shelter[0].fetch()) не скомпилируется, хотя в shelter[0] лежит собака:
error: value of type 'Animal' has no member 'fetch'Компилятор знает только тип массива — [Animal]. Что лежит в нём на самом деле, выясняется во время работы программы, и для этого есть проверка и приведение типа.
Проверка типа: is
Оператор is проверяет, относится ли экземпляр к нужному классу, и отвечает true или false:
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, как любой опционал:
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! применили там, где класс не гарантирован:
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?.