Основы Swift / 17.4. Расширения протоколов


Видео


Расширения протоколов
Протоколы могут быть расширены для предоставления реализации методов и свойств для подходящих типов. Это позволяет Вам определяет поведение протоколов в них самих, а не в каждом отдельном соответствующем типе или глобальной функции.
@{17.4\1\1}
Например, протокол RandomNumberGenerator может быть расширен для предоставления метода randomBool(), который использует результат требуемого метода random() для возвращения случайного значения типа Bool:
protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
@{17.4\1\2}
Путём создания расширения для протокола все подходящие типы автоматически получат эту реализацию метода без всяких дополнительных изменений:
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Выведет "Here's a random number: 0.37464991998171"
print("And here's a random Boolean: \(generator.randomBool())")
// Выведет "And here's a random Boolean: true"
Предоставление дефолтных реализаций
Вы можете использовать расширения протокола для предоставления реализации по-умолчанию для любого требования метода или вычисляемого свойства протокола. Если подходящий тип предоставляет свою собственную реализации требуемого метода или свойства, то именно эта имплементация будет использована вместо того, что предоставляется расширением.

Требования протокола с реализациями по-умолчанию, предоставляемыми расширениями, отличаются от опциональных требований протокола. И хотя в обоих случаях адаптирующим типам не необходимо предоставлять их собственные реализации требований, но требования с дефолтными реализациями могут быть вызваны без опциональных цепочек.
@{17.4\2\1}
Например, протокол PrettyTextRepresentable, который наследует протокол TextRepresentable может предоставить реализацию по-умолчанию своего требуемого свойства prettyTextualDescription для простого возврата результата доступа к свойству textualDescription:
protocol TextRepresentable {
    var textualDescription: String { get }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}
Добавления ограничений в расширения протоколов
Когда Вы определяете расширение протокола, Вы можете задать ограничения, которыми должны удовлетворять подходящие типы прежде, чем методы и свойства расширения станут доступны. Вы можете написать ограничения после имени расширяемого Вами протокола, используя блок шаблонов where.
@{17.4\2\2}
Например, Вы можете определить расширение протокола Collection, которое будет приметное к любой коллекции, чьи элементы соответствуют протоколу TextRepresentable из примера выше.
extension Collection where Iterator.Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
Свойство textualDescription возвращает текстовое описание всей коллекции, конкатенируя текстовые представления каждого элемента в коллекции в список, разделённый запятыми, заключённый в квадратные скобки.
@{17.4\2\3}
Рассмотрим раннюю структуру Hamster, которая удовлетворяет протоколу TextRepresentable и массив значений Hamster:
struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

let murrayTheHamster = Hamster(name: "Murray")
let morganTheHamster = Hamster(name: "Morgan")
let mauriceTheHamster = Hamster(name: "Maurice")
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
@{17.4\2\4}
Так как Array удовлетворяет протоколу Collection и элементы массива удовлетворяют протоколу TextRepresentable, то массив может использовать свойство textualDescription, чтобы получить текстовое представление своего содержимого:
print(hamsters.textualDescription)
// Выведет "[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]"
Если подходящий тип удовлетворяет требованиям для множества условные расширений, которые предоставляют имплементацию для одного метода или свойства, то Swift будет использовать реализацию с наиболее специфицированными ограничениями.