Any и AnyObject в Swift
Рассмотрим пример:
let age: Int = 42
Данный тип Int
является конкретным, константа age
является целым числом.
Но в программировании Swift есть случаи, когда тип может быть неспецифичным, неконкретным.
Swift предлагает две версии псевдонимов типа для работы с неопределенными типами:
Any
может отобразить экземпляр любого типа, включая функциональные типы.AnyObject
может отобразить экземпляр любого типа класса.
Следует использовать Any
и AnyObject
только тогда, когда явно необходимо поведение и особенности, которые эти типы предоставляют. Всегда лучше быть конкретным насчет типов, с которыми планируете работать в вашем коде.
Что из себя представляет Any?
Язык программирования Swift предоставляет нам два особых типа: Any
и AnyObject
. Они неспецифичны, потому что они могут быть чем угодно.
Давайте посмотрим на тип Any
. Следующий код определяет массив values
типа [Any]
:
let values: [Any] = ["Apple", 99, "Zaphod", -1]
На первый взгляд данный код не имеет смысла. Как может массив содержать несколько разных типов, таких как Int
и String
? Это возможно потому, что тип данного массива неспецифичен, то есть он имеет тип [Any]
.
При этом отдельные элементы массива values
используют свои собственные специфические типы:
let values: [Any] = ["Apple", 99, "Zaphod", -1]
for value in values {
switch value {
case is String:
print("\(value) имеет тип String!")
case is Int:
print("\(value) имеет тип Int!")
default:
print("Неизвестный тип!")
}
}
// Output
// Apple имеет тип String!
// 99 имеет тип Int!
// Zaphod имеет тип String!
// -1 имеет тип Int!
В приведенном выше примере использовался цикл for-in
для перебора элементов в массиве values
. Далее, проверяется тип каждого элемента массива с помощью конструкции switch
и в зависимости выполнения условий - выполняется код.
- Тип массива
values
имеет неспецифический тип ([Any]
). - Типы отдельных элементов массива являются конкретными типами, такими как
Int
иString
.
Можно использовать операторы is
и as
в кейсах конструкции switch
для определения типа константы или переменной, когда известно только то, что она принадлежит типу Any
или AnyObject
. Пример ниже перебирает элементы в массиве array
и запрашивает тип у каждого элемента с помощью конструкции switch
. Несколько случаев конструкции switch
привязывают их совпавшие значения к константе определенного типа, для того, чтобы потом можно было вывести значение на экран:
var array = [Any]()
array.append(0)
array.append(0.0)
array.append(42)
array.append(3.14159)
array.append("Hello")
array.append((2.0, 6.0))
array.append(Movie(name: "Wow", director: "Ivan Ivanov"))
array.append({ (name: String) -> String in "Hello, \(name)" })
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")
}
}
// Output
// 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 2.0, 6.0
// a movie called Wow, dir. Ivan Ivanov
// Hello, Michael
Тип Any
представляет собой значения любого типа, включая и опциональные типы. Swift предупредит, если используетcя опциональное значение в том месте, где ожидается тип Any
. Если действительно необходимо использовать опциональное значение в виде значения типа Any
, то можно использовать оператор as
, чтобы явно привести опциональный тип к Any
, как показано ниже.
let optionalNumber: Int? = 3
array.append(optionalNumber) // Warning
array.append(optionalNumber as Any) // No warning
Зачем использовать Any и AnyObject?
В Swift есть несколько аспектов в программировании, которые делают работу с типами более гибкой:
- Опционалы позволяют эффективно работать со значениями, которые могут иметь или не иметь значения.
- Дженерики помогают нам создавать заполнители, которые могут работать с разными типами.
- Протоколы помогают определять ограничения для определенных типов, независимо от того, какой тип принимает.
Опционалы, дженерики, протоколы, приведение типов, Any и AnyObject — все эти инструменты помогают более эффективно обрабатывать данные. Они делают наш код более понятным и выразительным, позволяя легче его расширять и поддерживать.
Any
и AnyObject
особенно полезны для значений, которые имеют смешанные неспецифические типы.
Пример:
let tweet: [String: Any] = [
"text": "Lorem ipsum dolor amet hoodie bicycle rights, 8-bit mixtape",
"likes": 42,
"retweets": ["@alex42", "@aplusk", "@beeblebrox"]
]
Созданный словарь tweet
смешивает значения разных типов. Ключи словаря имеют тип String
, а значения - Any
. Первое значение — это String
, второе — Int
, а третье — [String]
.
Что делать, если необходимо получить конкретное значение в словаре tweet
?
if let likes = tweet["likes"] {
print("У этого твита \(likes) лайков!")
}
В приведенном выше коде константа likes
имеет тип Int
. Используем опциональное связывание для получения значения по его ключу. При необходимости также можно сделать явное приведение типа:
if let likes = tweet["like"] as? Int {
...
}
Благодаря типу Any
можно объединить разные значения в одном словаре. При этом, не нужно создавать отдельный класс или тип для твита.
Разница между Any и AnyObject
Any
может представлять экземпляр любого типа, включая типы функций.AnyObject
может представлять экземпляр любого типа класса.
Используете Any
для всего, а AnyObject
только для классов. Но это еще не все.
Во-первых, важно понимать разницу между значимыми типами (Value Type) и ссылочными типами (Reference Type). В значимом типе значение копируется, когда вы передаете его в свой код, а в ссылочном типе нет. Классы являются ссылочными типами, и передача их в вашем коде просто создает ссылку на исходный объект.
Во-вторых, важно понимать роль Objective-C, предшественника Swift. В Objective-C вы можете использовать полиморфный нетипизированный id
-указатель для ссылки на любой тип объекта, что очень похоже на Any
и AnyObject
в Swift.
В Objective-C все объекты являются ссылочными типами. Доступ к ним осуществляется через указатели, и в Objective-C отсутствует концепция значимого типа.
Одной из особенностей Swift является его совместимость с Objective-C. Вы можете использовать код Objective-C в коде на Swift и наоборот. И вы можете использовать SDK, написанные на Objective-C, в ваших проектах на Swift.
Эта совместимость зависит, помимо прочего, от связывания типов Swift и типов Objective-C. Тип NSString
, например связан с типом Swift String
. В результате вы можете легко работать со строками между двумя языками.
Помните, что AnyObject
может работать только с классами? Это подразумевает, что AnyObject
— это ссылочный тип. Objective-C не имеет значимых типов и id
является ссылочным типом, потому что он использует указатели. И это имеет огромный недостаток: вы не можете извлечь выгоду из значимых типов Swift при взаимодействии с Objective-C id
. Поэтому Objective-C id
импортируется как Any
в Swift.
Когда вы должны выбрать один тип или другой?
Хорошей практикой будет использовать AnyObject
при работе с классами и Any
при работе со значимыми типами.
Как мы видели в предыдущих примерах, для массива с целыми числами и строками следует использовать Any
, потому что они являются значимыми типами. Вы можете использовать Any
с классами, но лучше использовать AnyObject
.