Обобщения в Swift

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

Стандартная библиотека Swift построена на обобщённом коде.

Типы Array и Dictionary в Swift — это обобщённые коллекции.

Вы можете создать массив Int, массив String или даже массив данных любого другого типа Swift.

Следующий пример — необобщенная функция exchange, используемая для обмена двух значений Int:

Пример

// Определить функцию для обмена двух переменных func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Перед обменом:\(numb1) и\(numb2)") swapTwoInts(&numb1, &numb2) print("После обмена:\(numb1) и\(numb2)")

Вывод выполнения вышеуказанной программы:

交换前数据: 100 和 200
交换后数据: 200 和 100

Приведенный выше пример применим только к обмену переменных целочисленного типа Int. Если вы хотите поменять местами два значения String или значения Double, вам придется снова написать соответствующую функцию, например swapTwoStrings(_:_:) и swapTwoDoubles(_:_:), как показано ниже:

Функции обмена значений String и Double

func swapTwoStrings(_ a: inout String, _ b: inout String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { let temporaryA = a a = b b = temporaryA }

Судя по приведённому выше коду, их функциональный код одинаков, отличаются только типы. В этом случае мы можем использовать обобщения, чтобы избежать написания повторяющегося кода.

Обобщения используют имя типа-заполнителя (здесь оно обозначено буквой T) для замены фактического имени типа (например, Int, String или Double).

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

После swapTwoValues следует имя типа-заполнителя (T), заключённое в угловые скобки (<T>). Эти угловые скобки сообщают Swift, что T — это имя типа-заполнителя в определении функции swapTwoValues(_:_:), поэтому Swift не будет искать фактический тип с именем T.

В следующем примере используется обобщённая функция exchange для обмена двух значений Int и String:

Пример

// Определить функцию для обмена двух переменных func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Перед обменом:\(numb1) и\(numb2)") swapTwoValues(&numb1, &numb2) print("После обмена:\(numb1) и\(numb2)") var str1 = "A" var str2 = "B" print("Перед обменом:\(str1) и\(str2)") swapTwoValues(&str1, &str2) print("После обмена:\(str1) и\(str2)")

Вывод выполнения вышеуказанной программы:

交换前数据:  100 和 200
交换后数据: 200 和 100
交换前数据:  A 和 B
交换后数据: B 和 A

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

Swift позволяет вам определять собственные обобщённые типы.

Пользовательские классы, структуры и перечисления работают с любым типом, точно так же, как использование Array и Dictionary.

Далее мы напишем обобщённый тип коллекции с именем Stack (stack). Стек позволяет добавлять новые элементы только в конец коллекции (называется pushing), а элементы можно удалять только с конца (называется popping).

Изображение объясняется слева направо следующим образом:

  • В стеке находятся три значения.
  • Четвёртое значение помещается на вершину стека.
  • Теперь в стеке четыре значения, и самое недавно помещённое значение находится на вершине.
  • Значение на вершине стека удаляется, или извлекается.
  • После удаления одного значения в стеке теперь снова только три значения.

Следующий пример — необобщённая версия стека, где в качестве примера используется стек Int:

Стек Int

struct IntStack { var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }

Эта структура использует свойство Array с именем items в стеке для хранения значений. Stack предоставляет два метода: push(_:) и pop(), используемые для добавления значений в стек и удаления значений из стека. Эти методы помечены как mutating, потому что им нужно изменять массив items структуры.

Приведённая выше структура IntStack может использоваться только для типа Int. Однако можно определить обобщённую структуру Stack, чтобы она могла работать со значениями любого типа.

Ниже приведена обобщённая версия того же кода:

Обобщённый Stack

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() print("Элемент String добавлен в стек:") stackOfStrings.push("google") stackOfStrings.push("пример") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("Извлечённый элемент:" + deletetos) var stackOfInts = Stack<Int>() print("Целочисленный элемент помещён в стек:") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);

Результат выполнения экземпляра:

字符串元素入栈: 
["google", "example"]
出栈元素: example
整数元素入栈: 
[1, 2]

Stack по сути такой же, как IntStack, где параметр типа-заполнитель Element заменяет фактический тип Int.

В приведённом выше примере Element используется как заполнитель в следующих трёх местах:

  • Созданиеitems-свойства, используяElementпустой массив этого типа для его инициализации.
  • Укажитеpush(_:)единственный параметр методаitemдолжен иметь типElementтип.
  • Укажитеpop()тип возвращаемого значения метода должен бытьElement-типом.

Расширение обобщённого типа

Когда вы расширяете обобщённый тип (с помощью ключевого слова extension), вам не нужно указывать список параметров типа в определении расширения. Что удобнее, список параметров типа, объявленный в исходном определении типа, доступен в расширении, а имена параметров из исходного типа используются как ссылки на параметры типа в исходном определении.

Следующий пример расширяет обобщённый тип Stack, добавляя вычисляемое свойство только для чтения с именем topItem, которое возвращает элемент на вершине текущего стека, не удаляя его из стека:

Обобщения

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings = Stack<String>() print("Элемент String добавлен в стек:") stackOfStrings.push("google") stackOfStrings.push("пример") if let topItem = stackOfStrings.topItem { print("Верхний элемент в стеке:\(topItem).") } print(stackOfStrings.items)

В примере свойство topItem возвращает опциональное значение типа Element. Когда стек пуст, topItem возвращает nil; когда стек не пуст, topItem возвращает последний элемент массива items.

Вывод выполнения вышеуказанной программы:

字符串元素入栈: 
栈中的顶部元素是:example.
["google", "example"]

Мы также можем указать связанный тип, расширив существующий тип.

Например, тип Array в Swift уже предоставляет метод append(_:), свойство count и сабскрипт, принимающий значение индекса типа Int для извлечения его элементов. Эти три возможности удовлетворяют требованиям протокола Container, поэтому вы можете расширить Array, просто объявив, что Array принимает этот протокол.

Следующий пример создаёт только пустое расширение:

extension Array: Container {}

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

Ограничения типов указывают, что параметр типа должен наследоваться от указанного класса или соответствовать определённому протоколу либо композиции протоколов.

Синтаксис ограничений типов

Вы можете записать ограничение типа после имени параметра типа, отделив его двоеточием, как часть цепочки параметров типа. Базовый синтаксис таких ограничений типов для обобщённых функций показан ниже (так же, как синтаксис для обобщённых типов):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

Эта функция имеет два параметра типа. Первый параметр типа, T, имеет ограничение типа, требующее, чтобы T был подклассом SomeClass; второй параметр типа, U, имеет ограничение типа, требующее, чтобы U соответствовал протоколу SomeProtocol.

Пример

Обобщения

// Необобщённая функция, найти индекс заданной строки в массиве func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // Возвращает значение индекса, если найдено return index } } return nil } let strings = ["google", "weibo", "taobao", "example", "facebook"] if let foundIndex = findIndex(ofString: "example", in: strings) { print("Индекс example равен\(foundIndex)") }

Индексация начинается с 0.

Вывод выполнения вышеуказанной программы:

example 的索引为 3

Связанные типы

В Swift ключевое слово associatedtype используется для настройки экземпляров связанного типа.

В следующем примере определяется протокол Container, который определяет связанный тип ItemType.

Протокол Container указывает только три возможности, которые должен предоставлять любой тип, соответствующий протоколу Container. Тип, соответствующий протоколу, также может предоставлять дополнительные возможности, удовлетворяя этим трём условиям.

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 协议的实现部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

var tos = Stack<String>()
tos.push("google")
tos.push("example")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

Вывод выполнения вышеуказанной программы:

["google", "example", "taobao"]
3

Предложения where

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

Вы можете определять ограничения для параметров в списке параметров с помощью условия where.

Вы можете написать инструкцию where сразу после списка параметров типа, а затем одно или несколько ограничений на связанные типы и/или одно или несколько отношений равенства между типами и связанными типами.

Пример

В следующем примере определяется обобщённая функция с именем allItemsMatch, используемая для проверки того, содержат ли два экземпляра Container одинаковые элементы в одинаковом порядке.

Если все элементы совпадают, возвращает true; в противном случае возвращает false.

Обобщения

// Протокол Container protocol Container { associatedtype ItemType // Добавить новый элемент в контейнер mutating func append(_ item: ItemType) // Получить количество элементов в контейнере var count: Int { get } // Получить каждый элемент в контейнере с помощью subscript с индексным типом Int subscript(i: Int) -> ItemType { get } } // // Обобщённый тип TOS, соответствующий протоколу Container struct Stack<Element>: Container { // Часть исходной реализации Stack<Element> var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Часть реализации протокола Container mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } // Расширение для использования Array как Container extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // Проверить, что два контейнера содержат одинаковое количество элементов if someContainer.count != anotherContainer.count { return false } // Проверить, равны ли элементы в каждой паре for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // Все элементы совпадают, вернуть true return true } var tos = Stack<String>() tos.push("google") tos.push("example") tos.push("taobao") var aos = ["google", "пример", "taobao"] if allItemsMatch(tos, aos) { print("Все элементы совпадают") } else { print("Элементы не совпадают") }

Вывод выполнения вышеуказанной программы:

Match all elements
Другие расширения