obo.dev

Типы Any и AnyObject

20 Dec 2022

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.


Еще полезные ссылки