Обобщения

Последнее обновление: 04.01.2018

Обобщения (generics) позволяют создавать гибкие конструкции без привязки к конкретным типам. Распространенным встроенным примером обобщений являются массивы в виде типа Array, который не привязан к конкретному типу, а может хранить и числа, и строки, и логические значения.

Обобщения полезны для создания универсальных функций, которые могут принимать значения разных типов. Например, функция обмена значениями:

func swap(_ a: inout Int, _ b: inout Int){
	
	let temp: Int = a
	a = b
	b = temp
}

var x: Int = 25
var y: Int = 14

swap(&x, &y)
print(x)	// 14

Данная функция прекрасно работает для двух чисел типа Int. Однако если нам понадобится какой-то функционал по обмену двух чисел типа Double, то нам придется писать новую функцию. Хотя по своему действию она ничем бы не отличалась бы от выше определенной функции. И было бы неплохо создать какую-то общую функцию сразу и для чисел Int, и для чисел Double.

И для решения данной проблемы мы можем использовать обобщения. Перепишем эту функцию с использованием обобщением:

func swap<T>(_ a: inout T, _ b: inout T){
	
	let temp: T = a
	a = b
	b = temp
}

Для создания обобщенной функции используется универсальный параметр типа, который идет после названия функции в угловых скобках. В данном случае универсальный параметр типа представляет букву T. В реальности это не обязательно должна быть буква T. Важно понимать, что эта буква T или универсальный параметр задает некоторый тип, который применяется в функции. При этом на момент определения функции мы можем не знать, что это за тип. Это просто некоторый тип. И параметры функции a и b теперь представляют значения именно этого типа.

Далее мы можем использовать эту функцию:

var x: Int = 25
var y: Int = 14
swap(&x, &y)
print(x)	// 14

Поскольку здесь мы передаем в функцию значения типа Int, то система автоматически в качестве параметра типа для этой функции будет использовать тип Int.

Но теперь в эту функцию мы можем передать и значения других типов. Например, типа Double:

var s1: Double = 10.2
var s2: Double = -3.6
swap(&s1, &s2)
print(s1)	// -3.6

Теперь система автоматически будет для параметра типа использовать тип Double.

Обобщенные типы

Кроме обобщенных функций также могут быть обобщенные или универсальные типы. Это типы, которые также используют универсальный параметр.

Например, для идентификации объекта часто используется свойство id - некий идентификатор, который отличает один объект от других. Для идентификатора, как правило, выбирается либо число, либо строка. Рассмотрим конкретный пример:

class User<T>{
	
	var id: T
	var name: String
	
	init(id: T, name: String){
		
		self.id = id
		self.name = name
	}
}

var tom: User = User(id: 12, name: "Tom")
var bob: User = User(id: "234nds", name: "Bob")

Для определения универсального обобщенного класса после имени класса в угловых скобках идет название универсального параметра: class User<T>

И в данном случае свойство id будет представлять значение типа T.

После определения класса мы создаем два объекта: tom и bob. Переменная tom в качестве id использует число, а переменная bob - строку.

И несмотря на то, что оба объекта представляют тип User, но с учетом универсального параметра переменная tom будет представлять тип User<Int>, а переменная tom - тип User<String>. И мы даже можем явно указать тип универсального параметра:

var tom: User<Int> = User<Int>(id: 12, name: "Tom")

Ограничения типа обобщения

Для универсального параметра мы можем установить ограничение (constraint). Ограничения могут быть полезны, если мы хотим, чтобы универсальный параметр мог представлять определенный класс или один из его производных классов. Например:

class Transport{
	
	func drive(){
		print("Транспорт едет")
	}
}
class Auto: Transport{
	
	override func drive(){
		
		print("Машина едет")
	}
}

func driveTransport<T: Transport>(_ transport: T){
	
	transport.drive()
}

var myAuto: Auto = Auto()
driveTransport(myAuto)	// Машина едет

Здесь определен класс транспортного средства Transport и производный от него класс машины - Auto.

И также здесь определена обобщенная универсальная функция driveTransport(), представляющая функцию вождения транспортного средства. Поскольку эта функция предусмотрена для любого транспортного средства, то задаем ограничение - тип Transport. Затем при вызове этой функции мы сможем передать в нее любой объект класса Transport или один из его производных классов, например, объект класса Auto.

Установка ограничения позволяет нам использовать у объектов этого типа методы и свойства. Так, в данном случае функция driveTransport() вызывает метод drive() переданного в функцию объекта.

наследование и обобщения

При наследовании от обощенного базового типа производный тип перенимает параметр базового типа. Но здесь есть две стратегии:

class User<T>{
    let id: T
    init(id: T){
        self.id = id
    }
    func displayId(){
        print(id)
    }
}
class Employee<T> : User<T>{}
class UserInt : User<Int>{}

let alice = Employee<String>(id: "5746fgg")
alice.displayId()

let bob = UserInt(id: 34)
bob.displayId()

В данном случае производный тип Employee также является обобщенным, и при создании его объекта мы можем типизировать его конкретным типом. А класс UserInt является необобщенным, он уже изначально типизирован типом Int.

При определении обобщений следует учитывать, что они не ковариантные. Например, в следующем случае мы получим ошибку:

struct Person { }
class Auto { }
class Truck : Auto{ }

let tom : Person<Auto> = Person<Truck>() // ! Ошибка компиляции

Хотя класс Truck наследуется от класса Auto, мы не можем присвоить переменной типа Person<Auto> объект Person<Truck>.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850