English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Swift предоставляет генерик для написания гибких и повторно используемых функций и типов.
Стандартная библиотека Swift построена на основе генериков.
Массивы и словари 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(_:_:), как показано ниже:
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 следуют placeholder для типа (T), заключенному в скобки (<T>
)。Этот尖括ок говорит Swift, что T - это placeholder для типа в определении функции swapTwoValues(_:_:), поэтому Swift не будет искать реальный тип с именем T.
Ниже приведен пример generics-функции 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 позволяет вам определять свои own generics-типы.
Пользовательские классы, структуры и энум работают с любыми типами, как и Array и Dictionary.
Далее мы напишем generics-тип под названием Stack (стек), который позволяет добавлять новые элементы только в конце коллекции (называемое добавлением в стек), и также можно удалять элементы только с конца (называемое выталкиванием).
В изображении слева направо объясняется следующим образом:
В стеке три значения.
Четвертое значение было добавлено на вершину стека.
Теперь в стеке есть четыре значения, последнее добавленное значение находится вверху.
Удаление верхнего значения стека, или называемое выталкиванием.
Удаление одного значения, теперь на стеке только три значения.
Ниже приведен пример неявного типа generics, например, тип Stack (стек) с типом Int:
struct IntStack {}} var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }
Эта структура использует атрибут массив items для хранения значений в стеке. Stack предоставляет два метода: push(_:), который добавляет значение в стек, и pop(), который удаляет значение из стека. Эти методы помечены как mutating, так как они требуют изменения массива items структуры.
Структура IntStack, приведенная выше, может использоваться только для типа Int. Однако, можно определить универсальную структуру 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("Вставка строки в стек:") stackOfStrings.push("google") stackOfStrings.push("w3codebox") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("Исходящий элемент: " + deletetos) var stackOfInts = Stack<Int>() print("Входные целые числа в стек: ") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);
Результат выполнения примера:
Вставка строки в стек: ["google", "w3codebox"] Исходящий элемент: w3codebox Входные целые числа в стек: [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("Вставка строки в стек:") stackOfStrings.push("google") stackOfStrings.push("w3codebox") if let topItem = stackOfStrings.topItem { print("Верхний элемент стека: \(topItem).") } print(stackOfStrings.items)
В этом примере свойство topItem возвращает опциональное значение типа Element. Когда стек пуст, topItem возвращает nil; когда стек не пуст, topItem возвращает последний элемент массива items.
Результат выполнения программы выше:
Вставка строки в стек: Верхний элемент стека: w3codebox. ["google", "w3codebox"]
Мы также можем определить связанный тип, расширяя существующий тип.
Например, тип Swift Array уже предоставляет метод append(_:], атрибут count и индекс с типом Int, который используется для поиска элементов. Эти три функции соответствуют требованиям протокола Container, поэтому вам нужно только просто объявить, что Array принимает этот протокол, чтобы расширить Array.
Ниже приведен пример создания пустого расширения:
extension Array: Container {}
Ограничение типа specifies a type parameter that must inherit from a specified class or follow a specific protocol or protocol composition.
Вы можете написать ограничение типа после имени типа параметра, разделенного двоеточием, в качестве части типа параметра. Основная грамматика ограничения типа для generics показана ниже (как и для generics типов):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // Это часть тела функции generics }
Эта функция имеет два типа параметров. Первый тип параметра 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", "w3codebox", "facebook"] if let foundIndex = findIndex(ofString: "w3codebox", in: strings) { print("Индекс w3codebox равен \(foundIndex)") }
Индексация начинается с 0.
Результат выполнения программы выше:
Индекс w3codebox равен 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("w3codebox") tos.push("taobao") // Список элементов print(tos.items) // Количество элементов print( tos.count )
Результат выполнения программы выше:
["google", "w3codebox", "taobao"] 3
Типовые ограничения могут обеспечить соответствие типов определениям ограничений универсальной функции или класса.
Вы можете определить ограничения параметров с помощью предложения where в списке параметров.
Вы можете написать предложение where, которое следует сразу за списком параметров типа, после которого следует одно или несколько ограничений для связанных типов, а также (или) одно или несколько отношений эквивалентности между типами и связанными типами.
Пример下方 определяет универсальную функцию allItemsMatch, которая используется для проверки того, содержат ли два примера Container одинаковые элементы в одинаковом порядке.
Если все элементы могут соответствовать, то возвращает true, в противном случае false.
// Протокол Container protocol Container { associatedtype ItemType // Добавление нового элемента в контейнер mutating func append(_ item: ItemType) // Получение количества элементов в контейнере var count: Int { get } // Получение каждого элемента контейнера по индексу типа 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("w3codebox") tos.push("taobao") var aos = ["google", "w3codebox", "taobao"] if allItemsMatch(tos, aos) { print("Все элементы совпадают") } print("Элементы не совпадают") }
Результат выполнения программы выше:
Совпадение всех элементов