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

Опциональные требования протокола

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

Вы можете определить опциональные требования для протокола. Эти требования не обязательно должны быть реализованы для соответствия протоколу. Опциональные требования должны иметь префиксный модификатор optional в качестве части определения протокола. Опциональные требования доступны для кода, который вы пишите, так что вы можете интерполировать его с кодом на Objective-C. И протокол и опциональное требование должны иметь атрибут @objc. Обратите внимание, что протоколы с маркировкой @objc могут приниматься только классами, но не структурами или перечислениями. 

Когда вы используете опциональное требование свойства или метода, то их тип автоматически становится опциональным. Например, тип метода (Int) -> String становится ((Int) -> String)?. Обратите внимание, что весь тип функции обернут в опциональное значение, а не только возвращаемое значение функции.

Опциональное требование протокола может быть вызвано при помощи опциональной цепочки, чтобы учесть возможность того, что требование не будет реализовано типом, который соответствует протоколу. Вы проверяете реализацию опционального метода, написав вопросительный знак после имени метода, когда он вызывается, например someOptionalMethod?(someArgument). Для более полной информации о опциональной последовательности читайте Опциональная последовательность.

Следующий пример определяет класс Counter, который использует источник внешних данных для предоставления значение их инкремента. Этот источник внешних данных определен протоколом CounterDataSource, который имеет два опциональных требования:

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

Протокол CounterDataSource определяет опциональное требование метода increment(forCount:) и опциональное требование свойства fixedIncrement. Эти требования определяют два разных способа для источника данных для предоставления подходящего значения инкремента для экземпляра Counter.

Заметка

Строго говоря, вы можете написать пользовательский класс, который соответствует протоколу CounterDataSource без реализации какого-либо требования этого протокола. Они оба опциональные, в конце концов. Хотя технически это допускается, но это не будет реализовываться для хорошего источника данных.

Класс Counter, определенный ниже, имеет опциональное свойство dataSource типа CounterDataSource?:

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

Класс Counter хранит свое текущее значение в переменном свойстве count. Класс Counter так же определяет метод increment, который увеличивает свойство count, каждый раз, когда вызывается этот метод.

Метод increment сначала пытается получить значение инкремента, заглядывая в реализацию метода increment(forCount:) в его источнике данных. Метод increment использует опциональную последовательность для попытки вызвать increment(forCount:) и передает текущее значение count как единственный аргумент метода.

Обратите внимание, что здесь всего два уровня опциональной последовательности. Первый - возможный источник данных dataSource, который может быть nil, так что dataSource имеет вопросительный знак после имени для индикации того, что метод increment(forCount:) может быть вызван только в том случае, если dataSource не nil. Второй уровень говорит нам о том, что даже если dataSource существует, у нас все равно нет гарантии того, что он реализует метод increment(forCount:), потому что это опциональное требование. Есть вероятность, что increment(forCount:) так же не реализован из-за опциональной цепочки. Вызов increment(forCount:) происходит только, если increment(forCount:) существует и не равен nil.  Именно по этой причине increment(forCount:) записан с вопросительным знаком после своего имени.

Так как вызов increment(forCount:) может провалиться по одной из этих двух причин, вызов возвращает нам значение типа опционального Int. Это верно даже если increment(forCount:) определено как возвращающее неопциональное значение Int в определении CounterDataSource.  Даже если подряд идут две опциональные операции, одна сразу после другой, то результат все равно будет иметь единсветннй завернутый опционал. Подробнее читайте Соединение нескольких уровней опциональных последовательностей.

После вызова increment(forCount:) опциональный Int, который он возвращает, разворачивается в константу amount, при помощи опциональной связки. Если опциональный Int содержит значения, то есть, если делегат и метод существуют, и метод вернул значение, то неразвернутое значение amount прибавляется в свойство count, и на этом реализация завершается.

Если же нет возможности получить значение из метода increment(forCount:) по причине dataSource равен nil или из-за того что у источника данных нет реализации метода increment(forCount:), а следовательно вместо этого метод increment пытается получить значение от источника данных fixedIncrement. Свойство fixedIncrement опциональное требование свойства, так что его имя так же написано в опциональной последовательности с вопросительным знаком, что служит индикатором того, что попытка получить значение этого свойства может привести к провалу. Как и раньше, возвращаемое значение является опциональной Int, даже тогда fixedIncrement определен как свойство типа неопционального Int, в качестве части определения протокола CounterDataSource.

Ниже приведена простая реализация CounterDataSource, где источник данных возвращает постоянное значение 3, каждый раз, как получает запрос. Это осуществляется благодаря тому, что реализуется опциональное требование свойства fixedIncrement:

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

Вы можете использовать экземпляр ThreeSource в качестве источника данных для новых экземпляров Counter:

var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12

Код , приведенный выше, создает новый экземпляр Counter, устанавливает его источник данных как экземпляр ThreeSource и вызывает метод счетчика increment четыре раза. Как и ожидалось, свойство счетчика увеличивается на три каждый раз, когда вызывается increment.

Ниже приведен более сложный источник данных TowardsZeroSource, который заставляет экземпляр Counter считать в сторону увеличения или уменьшения по направлению к нулю от текущего значения count:

class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

Класс TowardsZeroSource реализует опциональный метод increment(forCount:) из протокола CounterDataSource и использует значение аргумента count для определения направления следующего счета. Если count уже ноль, то метод возвращает 0, для отображения того, что дальнейших вычислений не требуется.

Вы можете использовать экземпляр TowardsZeroSource с уже существующим экземпляром Counter для отсчета от -4 и до 0. Как только счетчик достигает 0, вычисления прекращаются:

counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0

 

Swift: 
4.0