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

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

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

При определении протокола бывает нужно определить еще один или более связанных типов в качестве части определения протокола. Связанный тип дает плейсхолдер имени типу, который используется как часть протокола. Фактический тип, который будет использоваться связанным типом не указывается до тех пор, пока не будет принят протокол. Связанные типы указываются при помощи ключевого слова associatedtype.

Связанные типы в действии

Ниже приведен пример протокола Container, который объявляет связанный тип ItemType:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Протокол Container определяет три требуемых возможности, которые должен иметь любой контейнер:

  • Должна быть возможность добавлять новый элемент в контейнер при помощи метода append(_:).
  • Должна быть возможность получить доступ к количеству элементов в контейнере через свойство count, которое возвращает значение типа Int.
  • Должна быть возможность получить значение через индекс элемента, который принимает значение типа Int.

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

Любой тип, который удовлетворяет протоколу Container должен иметь возможность указывать на тип хранящихся элементов. Конкретно, он должен гарантировать, что только элементы правильного типа будут добавлены в контейнер, и должно быть ясно какой тип элементов будет возвращаться сабскриптом.

Для определения этих требований, протокол Container должен иметь способ ссылаться на тип элементов, которые он будет хранить, без указания типа элементов, которые может хранить конкретный контейнер. Протокол Container должен указать, что любое значение переданное в метод append(_:) должно иметь тот же тип, что и тип элементов контейнера, и что значение, возвращаемое сабскриптом контейнера, должно быть того же типа, что и элементы контейнера.

Чтобы добиться этого, протокол Container объявляет связанный тип ItemType, который записывается как associatedtype ItemType. Протокол не определяет для чего конкретно нужен алиас ItemType, потому что эта информация остается для любого соответствующего класса протоколу. Тем не менее, алиас ItemType предоставляет способ сослаться на тип элементов в Container и определить тип для использования метода append(_:) и сабскрипта, для того, чтобы гарантировать, что желаемое поведение любого Container имеет силу.

Ниже приведена версия неуниверсального типа IntStack, который адаптирован под протокол Container:

struct IntStack: Container {
    // исходная реализация IntStack
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // удовлетворение требований протокола Container
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

Тип IntStack реализует все три требования протокола Container, и в каждом случае оборачивает часть существующей функциональности типа IntStack для удовлетворения этих требований.

Более того, IntStack указывает, что для этой реализации контейнера, подходящий тип Item будет Int. Определение typealias Item = Int преобразует абстрактный тип ItemType в конкретный тип Int для этой реализации протокола Container.

Благодаря выводу типов Swift, вам фактически не нужно указывать конкретный тип Int для Item как часть определения IntStack. Так как IntStack соответствует протоколу Container, Swift может вывести соответствующий тип для Item, просто посмотрев на тип параметра item метода append(_:) и на тип возвращаемого значения сабскрипта. И на самом деле, если удалить строку кода typealias Item = Int, все будет продолжать работать, потому что все еще ясно какой тип должен быть использован для Item.

Вы так же можете создать универсальный тип 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]
    }
}

В этот раз тип параметра Element использован в качестве параметра item метода append(_:) и в качестве возвращаемого типа сабскрипта. Таким образом Swift может вывести, что Element подходящий тип для использования его в качестве ItemType для этого конкретного контейнера.

Расширение существующего типа для указания связанного типа

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

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

extension Array: Container {}

Существующий метод append(_:) типа Array и сабскрипт позволяют Swift выводить соответствующий тип для ItemType, точно так же как и для универсального типа Stack, который был приведен ранее. После определения расширения вы можете использовать Array как Container.

Использование аннотаций типа для ограничения связанного типа

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

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Для соответствия данной версии Container, каждому элементу Container нужно соответствовать/реализовывать протокол Equatable.

 

Swift: 
4.0