Видеокурсы по изучению языка программирования Swift. Подробнее

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

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Функция swapTwoValues(_:_:) и тип Stack могут работать с любыми типами. Однако иногда бывает нужно внедрить определенные ограничения типа на типы, которые могут быть использованы вместе универсальными функциями или универсальными типами. Ограничения типа указывают на то, что параметры типа должны наследовать от определенного класса или соответствовать определенному протоколу или композиции протоколов.

Для примера возьмем тип Dictionary, который имеет некоторые ограничения типов, которые могут быть использованы в качестве ключей. Как было описано в главе Словари, тип ключа словаря должен быть хешируемым. Таким образом он должен предоставить способ представить себя уникальным. Dictionary нужно, чтобы его ключи были хешируемыми, таким образом он может проверить, содержит ли конкретный ключ какое-либо значение. Без этого требования, Dictionary не в состоянии понять, должен ли он заменить или вставить значение для конкретного ключа, и не в состоянии найти значение для конкретного ключа, которое уже есть в словаре.

Такое требование внедряется ограничениями типа для типа ключа словаря, которое определяет, что каждый ключ должен соответствовать протоколу Hashable, специальному протоколу, который определен в стандартной библиотеке Swift. Все базовые типа Swift (String, Int, Double, Bool) по умолчанию являются хешируемыми типами.

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

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

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

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // тело функции…
}

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

Ограничение типа в действии

Ниже приведена неуниверсальная функция findIndex(ofString:in:), которая получает значение типа String для того, чтобы его найти, и массив значений типа String, внутри которого и будет происходить поиск. Функция findIndex(ofString:in:) возвращает опциональное значение Int, которое является индексом первого совпадения строки с элементом внутри массива или nil, которое означает отсутствие совпадения строки с каким-либо элементом массива:

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Функция findIndex(ofString:in:) может быть использована для поиска строкового значения в массиве строк:

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Выведет "The index of llama is 2"

Однако нахождение индекса совпадения значения в массиве бывает полезным не только для строк. Вы можете написать ту же функцию, но только в универсальной форме. Давайте напишем такую функцию и назовем ее findIndex, а все упоминания типа String заменим на тип T.

Вот как будет выглядит версия функции findIndex(ofString:in:) в универсальной форме findIndex(of:in:). Обратите внимание, что возвращаемый функцией тип все еще равен Int?, потому что функция возвращает опциональное значение индекса, а не опциональное значение элемента массива. Но будьте осторожны, так как эта функция не компилируется, по причинам, указанным после примера:

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Как мы и сказали, эта функция не компилируется. Проблема находится в строке “if value == valueToFind”. Не каждый тип в Swift может быть сравнен оператором равенства (==). Если вы создаете свой класс или структуру для отображения сложной модели данных, например, то смысл выражения “равен чему-то” для этого класса или структуры Swift не может додумать за вас. Из-за этого нет никакой гарантии того, что этот код будет работать для любого возможного класса T, и соответствующая ошибка компиляции выскакивает, когда вы пытаетесь скомпилировать код.

Но не все еще потеряно. Стандартная библиотека Swift определяет протокол Equatable, который требует любой соответствующий ей тип реализовывать равенство оператору равенства (==) и реализовывать неравенство оператору неравенства (!=), для того, чтобы значения этих типов можно было сравнивать между собой. Все стандартные типы Swift автоматически поддерживают протокол Equatable.

Любой тип, который удовлетворяет протоколу Equatable, может быть безопасно использован в функции findIndex(of:in:), потому что гарантирована поддержка оператора равенства и неравенства. Для отображения этого факта, вы пишите ограничение типа Equitable, как часть определения параметра типа, когда вы определяете функцию:

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Единственный параметр типа для функции findIndex(of:in:) записывается как T: Equatable, что означает “любой тип T, который соответствует протоколу Equatable”.

Теперь функция findIndex(of:in:) благополучно компилируется и может быть использована с любыми типами Equatable, например, String, Double:

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex опциональный Int не имеющий значения, потому что значения 9.3 нет в массиве
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex опциональный Int равный 2

 

Swift: 
3.0