Основы Swift / 16.1. Приведение типов


Видео


Приведение типов
Приведение типов - это способ проверить тип объекта или привести объект к другому надклассу или подклассу в иерархии данного класса.

Приведение типа в Swift реализовано в Swift с помощью операторов is и as. Эти два оператора предоставляют простой и выразительный способ проверить тип объекта и привести значение к другому типу.
Определение иерархии класса для приведения типов
Вы можете использовать приведение типов на иерархии классов и подклассов для проверки типа определённого объекта класса и для приведения этого объекта к другому классу той же иерархии. Три примера кода ниже определяют иерархию классов и массив, содержащий экземпляры объектов этих классов для использования в качестве примера приведения типов.
@{16.1\1\1}
Первый пример определяет новый базовый класс с названием MediaItem. Этот класс предоставляет базовый функционал для любого типа вещей, которые могут появиться в цифровой медиа-библиотеке. Конкретно, он определяет свойство name типа String и init(name:) конструктор. (Принимается, что все медиаобъекты, включая все фильмы и песни, имеют имена.)
class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}
@{16.1\1\2}
Следующий пример определяет два подкласса 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)
    }
}
@{16.1\1\3}
Последний снимет создаёт константный массив с названием library с типом, выведенным из инициализации его содержимым литерала массива. Проверка типов Swift способна вывести, что Movie и Song имеют общий надкласс MediaItem, так что он выведет тип массива library в [MediaType]:
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 - в противном случае.
@{16.1\1\4}
Пример ниже определяет две переменных, 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 library contains \(movieCount) movies and \(songCount) songs")
// Выведет "Media library contains 2 movies and 3 songs"
Этот пример итерируется через все элементы в массиве library. На каждом шаге цикл for-in задаёт константу item для следующего объекта MediaItem в массиве.

item is Movie вернёт true, если текущий объект MediaItem является объектом типа Movie, и false в противном случае. Аналогично, item is Song проверяет, является ли объект экземпляром класса Song. В конце цикла for-in значения movieCount и songCount содержат количество того, сколько объектов MediaItem каждого типа.
Довнкастинг или приведение вниз
Довнкастинг означает приведение типов вниз по в иерархии класса.

Константа или переменная определённого класса может на самом деле ссылаться на объект подкласса. Если Вы уверены, что это тот случай, Вы можете попытаться привести его к типу подкласса с помощью оператора приведения (as? или as!).

Так как дорнкастинг может провалиться, то оператор приведения типа имеет две различных формы. Условная форма as? возвращает опциональное значение для типа, к которому Вы пытаетесь выполнить приведение. Принудительная форма as! пытается выполнить довнкаст и принудительно извлечь результат в одном составном действии.

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

Используйте принудительную форму оператор приведения (as!), если только Вы уверены, что довнкаст будет успешен. Эта форма оператора вызовет ошибку времени выполнения, если Вы попытаетесь выполнить приведение них к некорректному типу.

Пример ниже итерируется через MediaItem в library и выводит подходящее описание для каждого элемента. Чтобы сделать это, ему нужно получить доступ к каждому элементу в качестве настоящего Movie или Song, а не просто MediaItem. Это необходимо, чтобы иметь доступ к свойства director или artist у Movie или Song для использования в описании.
@{16.1\1\5}
В этом примере каждый элемент в массиве может быть или 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 в массиве библиотеки. Для обработки этого пример ниже использует опциональное связывание для проверки того, содержит ли опциональный Movie какое-то значение (что значит, что довнкаст был успешен). Это опциональное связывание записывается в качестве "if let movie = item as? Movie", что может быть прочитано как:

"Попытаться получить доступ к item в качестве Movie. Если это будет успешно, установить новую опциональную константу с названием movie для объекта, хранимое в возвращаемом опционале Movie." Если дорнкастинг будет успешен, что свойства movie будут использовать для вывода описания для объекта Movie, включая имя его режисёра. Схожий принцип используется для проверки объектов Song и для вывода соответствующих описаний (включая имя исполнителя), когда бы объект Song не был бы найден в библиотеке.

Приведение на самом деле не изменяет объект и не меняет его значений. Подразумевающийся объект остаётся таким же, он просто принимается и используется как объект типа, к которому выполнялось приведение.
Приведение типов для Any и AnyObject
Swift предоставляет два специальных типа для работы с неопределёнными типами:
1. Any может представлять объект любого типа, включая функциональные типы.
2. AnyObject может представлять экземпляр любого типа-класса.

Используйте Any и AnyObject только тогда, когда Вам явно нужно поведение и возможности, ими предоставляемые. Всегда лучше использовать явные типы, когда Вы работаете с ними в коде.
@{16.1\1\6}
Здесь приведён пример использования 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), фильм "Охотники за приведениями" и выражение-замыкание, которое принимает строковое значение и возвращает другое строковое значение.
@{16.1\1\7}
Чтобы определить конкретный тип объекта или переменной, про которую известен лишь тип Any или AnyObject, Вы можете использовать паттерны is или as в кейсах инструкции switch. Пример ниже итерируется через элементы в массиве 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
@{16.1\1\8}
Тип Any представляет значения любого типа, включая опциональные типы. Swift выдаст Вам предупреждение, если Вы используете опциональное значение, где ожидается значение типа Any. Если Вам нужно действительно использовать оптимальное значение в качестве Any-значения, Вы можете использовать оператор as для явного приведения опционала к Any, как это показано ниже.
let optionalNumber: Int? = 3
things.append(optionalNumber)        // Предупреждение
things.append(optionalNumber as Any) // Отсутствие предупреждения