Приведение типов
Приведение типов - это способ проверить тип экземпляра и/или способ обращения к экземпляру так, как если бы он был экземпляром суперкласса или подкласса откуда-либо из своей собственной классовой иерархии.
Приведение типов в Swift реализуется с помощью операторов is и as. Эти два оператора предоставляют простой и выразительный способ проверки типа значения или преобразование значения к другому типу.
Вы также можете использовать приведение типов для проверки соответствия типа протоколу, подробнее об этом в разделе Проверка соответствия протоколу.
Определение классовой иерархии для приведения типов
Вы можете использовать приведение типов с иерархией классов и подклассов, чтобы проверить тип конкретного экземпляра класса и преобразовать тип этого экземпляра в тип другого класса в той же иерархии. Следующих три фрагмента кода определяют иерархию классов и массив, который содержит экземпляры этих классов, для использования в примере приведения типов.
Первый фрагмент кода определяет новый базовый класс MediaItem. Этот класс предоставляет базовую функциональность для любого вида элемента, который появляется в цифровой медиа библиотеке. А именно, он определяет свойство name типа String и инициализатор init name. (В этом примере считается, что все фильмы, песни или другие медиа штуковины имеют свойство name или попросту имя.)
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
Следующий фрагмент определяет два подкласса класса MediaItem. Первый подкласс - Movie, он инкапсулирует дополнительную информацию о фильмах. Он добавляет свойство director поверх базового класса MediaItem с соответствующим инициализатором. Второй подкласс - Song. Этот подкласс добавляет свойство artist и инициализатор поверх базового класса:
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)
}
}
Последний отрывок кода создает неизменяемый массив library, который содержит два экземпляра Movie и три экземпляра Song. Тип library выведен во время инициализации массива литералом массива. Механизм проверки типов Swift делает вывод, что Movie, Song имеют общий суперкласс MediaItem, так что тип массива library становится [MediaItem]:
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// тип "library" выведен как [MediaItem]
Элементы, которые хранятся в library все еще экземпляры Movie и Song на самом деле. Однако, если вы переберете элементы массива, то они все будут одного типа MediaItem, а не Movie или Song. Для того чтобы работать с ними как с исходными типами, вам нужно проверить их типы или привести к другому типу, как указано далее.
Проверка типа
Используйте оператор проверки типа (is) для проверки того, соответствует ли тип экземпляра типам какого-то определенного подкласса. Оператор проверки типа возвращает true, если экземпляр имеет тип конкретного подкласса, false, если нет.
Пример ниже определяет две переменные movieCount и songCount, которые считают число экземпляров Movie и экземпляров Song в массиве library:
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("В Media библиотеке содержится \(movieCount) фильма и \(songCount) песни")
// Выведет "В Media библиотеке содержится 2 фильма и 3 песни"
Пример перебирает все элементы массива library. На каждую итерацию цикла for-in константа item инициализируется следующим значением типа MediaItem из массива library.
Выражение item is Movie возвращает true, в том случае, если текущий item типа MediaItem является экземпляром Movie, и это выражение возвращает false, если не является экземпляром Movie. Аналогично item is Song проверяет является ли item экземпляром Song. В конце цикла for-in значения movieCount и songCount содержат количество экземпляров MediaItem каждого типа.
Понижающее приведение
Константа или переменная определенного класса может фактически ссылаться на экземпляр подкласса. Там, где вы считаете, что это тот случай, вы можете попробовать привести тип к типу подкласса при помощи оператора понижающего приведения (as? или as!).
Из-за того, что понижающее приведение может провалиться, оператор приведения имеет две формы. Опциональная форма (as?), которая возвращает опциональное значение типа, к которому вы пытаетесь привести. И принудительная форма (as!), которая принимает попытки понижающего приведения и принудительного разворачивания результата в рамках одного составного действия.
Используйте опциональную форму оператора понижающего приведения (as?), когда вы не уверены, что ваше понижающее приведение выполнится успешно. В этой форме оператор всегда будет возвращать опциональное значение, и значение будет nil, если понижающее приведение будет не выполнимо. Так же это позволяет вам проверить успешность понижающего приведения типа.
Используйте принудительную форму оператора понижающего приведения (as!), но только в тех случаях, когда вы точно уверены, что понижающее приведение будет выполнено успешно. Эта форма оператора вызовет ошибку исполнения, если вы попытаетесь таким образом привести к некорректному типу класса.
Пример ниже перебирает элементы MediaItem в массиве library и выводит соответствующее описание для каждого элемента. Чтобы сделать это, ему нужно получить доступ к каждому элементу как Movie или Song, а не просто как к MediaItem. Это необходимо для того, чтобы был доступ к свойствам director, artist, которые пригодятся нам в описании.
В этом примере каждый элемент массива может быть либо Movie, либо Song. Вы не знаете наперед какой класс вам нужно использовать для каждого элемента, так что логично будет использовать опциональную форму оператора понижающего приведения (as?) для проверки возможности понижающего приведения к конкретному подклассу конкретного элемента массива:
for item in library {
if let movie = item as? Movie {
print("Movie: \(movie.name), dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: \(song.name), by \(song.artist)")
}
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
Пример начинается с попытки понижающего приведения текущего элемента item в качестве Movie. Так как item является экземпляром MediaItem, то этот элемент может быть Movie, но он также может быть и Song или даже просто базовым MediaItem. Из-за этой неопределенности мы и используем опциональную форму оператора (as?). Результат выражения item as? Movie является тип Movie? или “опциональный Movie”.
Понижающее приведение к Movie проваливается, когда оно применимо к экземплярам Song в массиве library. Объединив это все, мы получаем, что пример выше использует опциональную привязку для проверки наличия значения у опционального Movie (то есть, чтобы выяснить успешность проведенной операции). Опциональная привязка записана выражением вида “if let movie = item as? Movie”, что может быть прочитано так:
“Пробуем получить доступ к item в качестве Movie. Если доступ успешен, то присвоим временную константу movie значение, которое мы получили из опционального Movie.”
Если понижающее приведение прошло успешно, то свойства movie используются в качестве вывода описания для экземпляра Movie, включая имя director. Аналогичный принцип используется при проверке экземпляров Song и при выводе описания (включая имя artist), как только находится элемент Song в массиве library.
Заметка
Приведение не изменяет экземпляра или его значений. Первоначальный экземпляр остается тем же. Просто после приведения типа с экземпляром можно обращаться (и использовать свойства) именно так как с тем типом, к которому его привели.
Приведение типов для Any и AnyObject
Swift предлагает две версии псевдонимов типа для работы с неопределенными типами:
- Any может отобразить экземпляр любого типа, включая функциональные типы.
- AnyObject может отобразить экземпляр любого типа класса.
Используйте Any и AnyObject только тогда, когда вам явно нужно поведение и особенности, которые они предоставляют. Всегда лучше быть конкретным насчет типов, с которыми вы ожидаете работать в вашем коде.
Вот пример использования Any с различными типами, в том числе функциональными типами и не классовыми типами. В этом примере создается массив с именем things, который может хранить значения типа Any:
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
Массив things содержит два значения типа Int, два значения типа Double, значение типа String, кортеж типа (Double, Double), кино “Ghostbusters” и выражение замыкания, которое принимает значение String и возвращает другое значение String.
Вы можете использовать операторы is и as в кейсах конструкции switch для определения типа константы или переменной, когда известно только то, что она принадлежит типу Any или AnyObject. Пример ниже перебирает элементы в массиве things и запрашивает тип у каждого элемента с помощью конструкции switch. Несколько случаев конструкции switch привязывают их совпавшие значения к константе определенного типа, для того, чтобы потом можно было вывести значение на экран:
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
Заметка
Тип Any представляет собой значения любого типа, включая и опциональные типы. Swift предупредит вас, если вы используете опциональное значение в том месте, где ожидается тип Any. Если вы действительно хотите использовать опциональное значение в виде значения типа Any, то вы можете использовать оператор as, чтобы явно привести опционал к Any, как показано ниже.
let optionalNumber: Int? = 3 things.append(optionalNumber) // Warning things.append(optionalNumber as Any) // No warning