Приведення типів
Приведення типів – це спосіб перевірити тип екземпляру, або працювати з екземпляром як з іншим батьківським класом чи класом-нащадком з іншого місця в ієрархії класів.
Приведення типів у Swift реалізоване у вигляді операторів is
та as
. Ці оператори дають простий та виразний спосіб перевірити тип значення чи привести значення до іншого типу.
За допомогою приведення типу можна перевірити, чи підпорядковується тип протоколу, як описано у підрозділі Перевірка на підпорядкованість протоколу.
Визначення ієрархії класів для приведення типів
Приведення типів можна використовувати з ієрархією класів для перевірки типу певного екземпляра класу, та для приведення екземпляру до іншого класу в цій ієрархії. У трьох зразках коду далі оголошено ієрархію класів та масив, що містить екземпляри цих класів, для подальшого використання у прикладах з приведенням типів.
У першому фрагменті коду визначено базовий клас на ім’я MediaItem
. Цей клас реалізовує базову функціональність елементу цифрової медіа-бібліотеки. Зокрема, в ньому оголошено властивість name
типу String
для представлення назви елементу, та ініціалізатор init 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
, і тому визначає [MediaItem]
типом масиву library
:
let library = [
Movie(name: "Дике поле", director: "Сергій Жадан"),
Song(name: "Земля", artist: "Riffmaster"),
Movie(name: "Захар Беркут", director: "Ахтем Сейтаблаєв"),
Song(name: "100% плагіат", artist: "Тартак"),
Song(name: "Аркан", artist: "Карна")
]
// тип масиву "library" визначено як [MediaItem]
Елементи, що зберігаються в бібліотеці, все ще є екземплярами 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("Медіа-бібліотека містить \(movieCount) фільми та \(songCount) пісні")
// Надрукує "Медіа-бібліотека містить 2 фільми та 3 пісні"
У цьому прикладі йде ітерування всіх елементів масиву library
. Під час кожного проходу, цикл for
-in
присвоює константі item
наступний нащадок MediaItem
у масиві.
item is Movie
повертає true
якщо поточний MediaItem
є екземпляром Movie
, та false
в іншому випадку. Аналогічно, item is Song
перевіряє, чи є item
екземпляром класу Song
. Після виконання циклу for
-in
, значення movieCount
та songCount
містять кількості екземплярів кожного з нащадків MediaItem
.
Приведення типів
Константа чи змінна певного типу може фактично посилатись на екземпляр класу-нащадку за лаштунками. Якщо є певна впевненість у цьому в конкретному випадку, можна привести тип до типу класу-нащадку, за допомогою оператора приведення типів (as?
or as!
, дослівно “як”).
Оскільки приведення типів може завершитись невдало, оператор приведення типів має дві різні форми. Умовна форма, as?
, повертає опціональне значення типу, до якого приводиться екземпляр. Примусова форма, as!
, пробує привести тип і після цього примусово розгортає опціонал – все це в ході єдиної монолітної операції.
Слід користуватись умовною формою оператора приведення типів (as?
), коли немає впевненості в тому, що приведення типів спрацює. Ця форма оператора завжди повертає опціональне значення, і у випадках, коли приведення типів неможливе, буде повернуто значення nil
. Це дозволяє перевірити, чи було приведення типів вдалим.
Примусовою формою оператору (as!
) слід користуватись лише при повній впевненості, що приведення типів завжди буде успішним. Ця форма оператора призводить до помилки часу виконання при спробі приведення до некоректного типу.
У прикладі нижче відбувається ітерування по елементах MediaItem
у масиві library
, та друкується опис кожного елементу. Для цього потрібно звертатись до кожного з елементів як до екземпляра Movie
чи Song
, а не просто як до MediaItem
. Це необхідно для того, щоб мати доступ до властивостей director
чи artist
екземплярів Movie
чи Song
відповідно, для використання їх в описі.
В цьому прикладі, кожен елемент у масиві може бути екземпляром Movie
, або екземпляром Song
. Ми не знаємо наперед, який клас використовувати для кожного елементу, і тому в ітераціях циклу доречно буде користуватись умовною формою оператора приведення типів (as?
):
for item in library {
if let movie = item as? Movie {
print("Фільм: \(movie.name), режисер: \(movie.director)")
} else if let song = item as? Song {
print("Пісня: \(song.name), виконує: \(song.artist)")
}
}
// Фільм: Дике поле, режисер: Сергій Жадан
// Пісня: Земля, виконує: Riffmaster
// Фільм: Захар Беркут, режисер: Ахтем Сейтаблаєв
// Пісня: 100% плагіат, виконує: Тартак
// Пісня: Аркан, виконує: Карна
Даний приклад починається зі спроби привести поточний item
до типу Movie
. Оскільки елемент є екземпляром 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
) щоразу, коли в масиві library
знайдено екземпляр Song
.
Примітка
Приведення типу фактично ніяк не змінює екземпляр чи його властивості. Екземпляр, на котрий посилається змінна, залишається тим же; просто до нього звертаються як до екземпляру того типу, до якого відбулось приведення.
Приведення типів для 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("привіт")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Привіт, \(name)" })
Масив things
містить два значення Int
, два значення Double
, рядок, кортеж типу (Double, Double)
, фільм “Ghostbusters”, та замикання, котре приймає рядок, і повертає інший рядок.
Щоб з’ясувати фактичний тип константи чи змінної типу Any
чи AnyObject
, можна використовувати оператори is
або as
у шаблоні випадків інструкції switch
. У прикладі нижче йде ітерування елементів масиву things
та перебір можливих типів у інструкції switch
. Кілька випадків switch
прив’язують значення, що співпало, до константи відповідного типу, для того щоб надрукувати це значення:
for thing in things {
switch thing {
case 0 as Int:
print("нуль як Int")
case 0 as Double:
print("нуль як Double")
case let someInt as Int:
print("цілочисельне значення \(someInt)")
case let someDouble as Double where someDouble > 0:
print("додатнє дійсне значення \(someDouble)")
case is Double:
print("інше дійсне значення яке ми не хочемо друкувати")
case let someString as String:
print("рядок \"\(someString)\"")
case let (x, y) as (Double, Double):
print("точка (x, y) з координатами \(x), \(y)")
case let movie as Movie:
print("фільм з назвою \(movie.name), режисер: \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Василина"))
default:
print("щось інше")
}
}
// нуль як Int
// нуль як Double
// цілочисельне значення 42
// додатнє дійсне значення 3.14159
// рядок "привіт"
// точка (x, y) з координатами 3.0, 5.0
// фільм з назвою Ghostbusters, режисер: Ivan Reitman
// Привіт, Василина
Примітка
Тип
Any
представляє значення будь-якого типу, включно з опціональними типами. Swift дає попередження, якщо використати опціональне значення там, де очікуєтьсяAny
. Якщо дійсно потрібно використати опціональне значення якAny
, можна використати операторas
для явного приведення опціоналу доAny
, як показано нижче.let optionalNumber: Int? = 3 things.append(optionalNumber) // Попередження things.append(optionalNumber as Any) // Нема попередження