Swift Language
Дженерики
Поиск…
замечания
Общий код позволяет писать гибкие, многократно используемые функции и типы, которые могут работать с любым типом, в зависимости от требований, которые вы определяете. Вы можете написать код, который избегает дублирования и выражает свое намерение четким, абстрактным образом.
Generics - одна из самых мощных функций Swift, и большая часть стандартной библиотеки Swift построена с общим кодом. Например, типы
Array
Swift иDictionary
являются общими коллекциями. Вы можете создать массив, который содержит значенияInt
, или массив, который содержит значенияString
, или действительно массив для любого другого типа, который может быть создан в Swift. Аналогичным образом, вы можете создать словарь для хранения значений любого указанного типа, и нет ограничений на то, что может быть этим типом.Источник: Быстрый язык программирования Apple
Ограничение общих типов закладок
Можно заставить параметры типа универсального класса реализовать протокол , например Equatable
class MyGenericClass<Type: Equatable>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func valueEquals(anotherValue: Type) -> Bool{
return self.value == anotherValue
}
}
Всякий раз, когда мы создаем новый MyGenericClass
, параметр type должен реализовывать Equatable
протокол (гарантируя, что параметр типа можно сравнить с другой переменной того же типа, используя ==
)
let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid
// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2])
let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false
// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))
Основы дженериков
Generics - это заполнители для типов, позволяющие писать гибкий код, который можно применять для нескольких типов. Преимущество использования дженериков над Any
заключается в том, что они все еще позволяют компилятору обеспечить сильную безопасность типов.
Общий заполнитель определяется в угловых скобках <>
.
Общие функции
Для функций этот заполнитель помещается после имени функции:
/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
return arc4random_uniform(2) == 0 ? a : b
}
В этом случае общим заполнителем является T
Когда вы приходите на вызов функции, Swift может вывести тип T
для вас (поскольку он просто выступает в качестве заполнителя для фактического типа).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Здесь мы передаем две целые функции. Поэтому Swift выводит T == Int
- таким образом, сигнатура функции выводится как (Int, Int) -> Int
.
Из-за сильной безопасности типа, которую предлагают дженерики - оба аргумента и возврат функции должны быть одного типа. Поэтому следующее не будет компилироваться:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
Общие типы
Чтобы использовать дженерики с классами , структурами или перечислениями , вы можете определить общий заполнитель после имени типа.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
Этот общий заполнитель потребует тип, когда вы приступите к использованию класса Bar
. В этом случае это можно сделать из инициализации init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Здесь общий заполнитель T
определяется как тип String
, создавая таким образом экземпляр Bar<String>
. Вы также можете указать тип явно:
let bar = Bar<String>(baz: "a string")
При использовании с типом данный общий заполнитель будет сохранять свой тип на протяжении всего срока службы данного экземпляра и не может быть изменен после инициализации. Поэтому, когда вы получаете доступ к свойству baz
, он всегда будет иметь тип String
для данного экземпляра.
let str = bar.baz // of type String
Передача общих типов
Когда вы сталкиваетесь с общими типами, в большинстве случаев вы должны быть явно о типичном типе-заполнителе, который вы ожидаете. Например, как вход функции:
func takeABarInt(bar:Bar<Int>) {
...
}
Эта функция будет принимать только Bar<Int>
. Попытка передать экземпляр Bar
где общий тип заполнителя не является Int
приведет к ошибке компилятора.
Именование общего места
Общие имена заполнителей не ограничиваются только отдельными буквами. Если данный заполнитель представляет собой содержательную концепцию, вы должны дать ему описательное имя. Например, в Array
Swift имеется общий заполнитель, называемый Element
, который определяет тип элемента заданного экземпляра Array
.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
Примеры общего класса
Общий класс с типом параметра Type
class MyGenericClass<Type>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func setValue(value: Type){
self.value = value
}
}
Теперь мы можем создавать новые объекты с использованием параметра типа
let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024
Дженерики также могут быть созданы с несколькими параметрами типа
class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
var value1: TypeOne
var value2: TypeTwo
var value3: TypeThree
init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
self.value1 = value1
self.value2 = value2
self.value3 = value3
}
func getValueOne() -> TypeOne{return self.value1}
func getValueTwo() -> TypeTwo{return self.value2}
func getValueThree() -> TypeThree{return self.value3}
}
И используется таким же образом
let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159
Наследование общего класса
Родовые классы можно унаследовать:
// Models
class MyFirstModel {
}
class MySecondModel: MyFirstModel {
}
// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
func doSomethingWithModel(model: T) {
// Do something here
}
}
class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
override func doSomethingWithModel(model: T) {
super.doSomethingWithModel(model)
// Do more things here
}
}
Использование генераторов для упрощения функций массива
Функция, расширяющая функциональность массива путем создания объектно-ориентированной функции удаления.
// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
if let index = self.index(of: element ) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}
использование
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
Используйте эту функцию для удаления элемента без необходимости индексирования. Просто передайте объект для удаления.
myArray.remove(2)
print(myArray)
// Prints [1,3]
Использование дженериков для повышения безопасности типов
Давайте рассмотрим этот пример без использования дженериков
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
Объявление протокола кажется прекрасным, если вы его не используете.
let myTestObject = TestObject.from(myJson) as? TestObject
Почему вы должны TestObject
результат в TestObject
? Из-за Any
типа возврата в объявлении протокола.
Используя дженерики, вы можете избежать этой проблемы, которая может вызвать ошибки времени выполнения (и мы не хотим их использовать!)
protocol JSONDecodable {
associatedtype Element
static func from(_ json: [String: Any]) -> Element?
}
struct TestObject: JSONDecodable {
static func from(_ json: [String: Any]) -> TestObject? {
}
}
let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`
Расширенные ограничения типов
Можно указать несколько ограничений типа для генериков с использованием предложения where
:
func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
Также допустимо написать предложение where
после списка аргументов:
func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
Расширения могут быть ограничены типами, удовлетворяющими условиям. Функция доступна только для экземпляров, которые удовлетворяют условиям типа:
// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
// We could also use "self.index(of: element)" here, as "index(of:_)"
// is also defined in an extension with "where Element: Equatable".
// For the sake of this example, explicitly make use of the Equatable.
if let index = self.index(where: { $0 == element }) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}