Swift Language
Дженерики
Поиск…
замечания
Общий код позволяет писать гибкие, многократно используемые функции и типы, которые могут работать с любым типом, в зависимости от требований, которые вы определяете. Вы можете написать код, который избегает дублирования и выражает свое намерение четким, абстрактным образом.
Generics - одна из самых мощных функций Swift, и большая часть стандартной библиотеки Swift построена с общим кодом. Например, типы
ArraySwift и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")
}
}
}