Как определить дополнительные методы в протоколе Swift?

359

Возможно ли это в Swift? Если нет, то есть ли обходной путь для этого?

Selvin
источник
1
Я, наконец, мотивировал себя обновить свой ответ, вы можете пересмотреть его принятие.
акашивский
@akashivskyy Я принимаю ваш ответ, потому что он лучше демонстрирует все доступные варианты и их плюсы и минусы.
Сельвин

Ответы:

506

1. Использование реализаций по умолчанию (предпочтительно).

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        /* return a default value or just leave empty */
    }
}

struct MyStruct: MyProtocol {
    /* no compile error */
}

преимущества

  • Нет времени выполнения Objective C (ну, по крайней мере, явно). Это означает, что вы можете соответствовать структурам, перечислениям и не NSObjectклассам. Кроме того, это означает, что вы можете воспользоваться мощной системой генериков.

  • Вы всегда можете быть уверены, что все требования выполняются при обнаружении типов, соответствующих такому протоколу. Это всегда либо конкретная реализация, либо реализация по умолчанию. Так ведут себя «интерфейсы» или «контракты» на других языках.

Недостатки

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

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

    protocol SomeParserDelegate {
        func validate(value: Any) -> Bool
    }

    Если вы предоставляете реализацию по умолчанию, которая просто возвращает true- это нормально на первый взгляд. Теперь рассмотрим следующий псевдокод:

    final class SomeParser {
        func parse(data: Data) -> [Any] {
            if /* delegate.validate(value:) is not implemented */ {
                /* parse very fast without validating */
            } else {
                /* parse and validate every value */
            }
        }
    }

    Нет способа реализовать такую ​​оптимизацию - вы не можете знать, реализует ли ваш делегат метод или нет.

    Несмотря на то, что существует ряд различных способов преодоления этой проблемы (использование необязательных замыканий, разные объекты-делегаты для разных операций и многие другие), этот пример ясно представляет проблему.


2. Использование @objc optional.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
    /* no compile error */
}

преимущества

  • Реализация по умолчанию не требуется. Вы просто объявляете необязательный метод или переменную, и вы готовы к работе.

Недостатки

  • Это серьезно ограничивает возможности вашего протокола, требуя совместимости всех соответствующих типов с Objective-C. Это означает, что только классы, которые наследуются, NSObjectмогут соответствовать такому протоколу. Нет структур, нет перечислений, нет связанных типов.

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

akashivskyy
источник
17
Посмотрите на улучшенный способ дополнительных методов в Swift с использованием расширения (ниже)
Даниэль Канаан
1
И как можно проверить поддержку необязательного метода протокола в экземпляре? respondsToSelector?
devios1
3
Только не делай этого! Swift не поддерживает необязательный метод в протоколах по причине.
fpg1503
2
Этот метод не поддерживает опциональные параметры. Т.е. ты не можешь сделать этоoptional func doSomething(param: Int?)
SoftDesigner
4
Конечно, этот ответ по сути неправильный в наши дни, годы спустя. Сегодня в Swift вы просто добавляете расширение для реализации по умолчанию, это основной аспект Swift. (Как показывают все современные ответы ниже.) В настоящее время было бы неправильно добавлять флаг objc.
Толстяк
394

В Swift 2 и далее можно добавлять реализации протокола по умолчанию. Это создает новый способ дополнительных методов в протоколах.

protocol MyProtocol {
    func doSomethingNonOptionalMethod()
    func doSomethingOptionalMethod()
}

extension MyProtocol {
    func doSomethingOptionalMethod(){ 
        // leaving this empty 
    }
}

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

Я написал небольшое резюме здесь: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
источник
2
Это, наверное, самый чистый способ сделать это в Swift. Жаль, что это не работает до Swift 2.0.
Энтальпи
13
@ MattQuiros Я обнаружил, что вам действительно нужно объявить функцию в определении протокола, в противном случае функция расширения без операции не будет переопределена в ваших классах, которые соответствуют протоколу.
Ян Пирс,
3
@IanPearce является правильным, и это, кажется, было разработано. В лекции «Протоколно-ориентированное программирование» (408) на WWDC они рассказывают о методах в основном протоколе, которые представляют собой «точки настройки», предлагаемые для соответствующих типов. Требуемая точка настройки не получает определения в расширении; необязательный пункт делает. Методы протокола, которые, как правило, не следует настраивать, полностью объявляются / определяются в расширении, чтобы запретить настраиваемые типы для настройки, если только вы специально не приведете к его dynamicType, чтобы показать, что вы хотите пользовательскую реализацию конформера.
Матиас
4
@FranklinYu Вы можете сделать это, но затем вы вносите изменения в свой дизайн API с помощью «throws», где это фактически не требуется. Мне больше нравится идея «микро-протоколов». Например, каждый метод подтверждает один протокол, а затем вы можете проверить: является ли объект протоколом
Darko
3
@ Antoine Я думаю, вы должны сделать функцию в расширении общедоступной, так как функции в протоколах общедоступны по определению. Ваше решение не будет работать, когда протокол будет использоваться за пределами его модуля.
Вадим Айзенберг
39

Так как есть несколько ответов о том, как использовать необязательный модификатор и атрибут @objc для определения необязательного протокола требований, я приведу пример того, как использовать расширения протокола для определения необязательного протокола.

Ниже код Swift 3. *.

/// Protocol has empty default implementation of the following methods making them optional to implement:
/// `cancel()`
protocol Cancelable {

    /// default implementation is empty.
    func cancel()
}

extension Cancelable {

    func cancel() {}
}

class Plane: Cancelable {
  //Since cancel() have default implementation, that is optional to class Plane
}

let plane = Plane()
plane.cancel()
// Print out *United Airlines can't cancelable*

Обратите внимание, что методы расширения протокола не могут быть вызваны кодом Objective-C, и хуже всего то, что команда Swift не исправит это. https://bugs.swift.org/browse/SR-492

Eddie.Dou
источник
1
Отличная работа! Это должен быть правильный ответ, так как он решает проблему без вовлечения Objective C.
Лукас
действительно хороший!
tania_S
из быстрой документации - «Требования к протоколу с реализациями по умолчанию, предоставляемыми расширениями, отличаются от необязательных требований к протоколу. Хотя соответствующие типы не должны обеспечивать свою собственную реализацию, требования с реализациями по умолчанию могут вызываться без необязательного сцепления».
Mec Os
34

Другие ответы, связанные с пометкой протокола как «@objc», не работают при использовании специфичных для swift типов.

struct Info {
    var height: Int
    var weight: Int
} 

@objc protocol Health {
    func isInfoHealthy(info: Info) -> Bool
} 
//Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"

Чтобы объявить необязательные протоколы, которые хорошо работают с swift, объявите функции как переменные вместо func.

protocol Health {
    var isInfoHealthy: (Info) -> (Bool)? { get set }
}

А затем реализовать протокол следующим образом

class Human: Health {
    var isInfoHealthy: (Info) -> (Bool)? = { info in
        if info.weight < 200 && info.height > 72 {
            return true
        }
        return false
    }
    //Or leave out the implementation and declare it as:  
    //var isInfoHealthy: (Info) -> (Bool)?
}

Затем вы можете использовать "?" проверить, была ли реализована функция

func returnEntity() -> Health {
    return Human()
}

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
источник
3
С этим решением вы не можете получить доступ к себе из любой функции протокола. Это может вызвать проблемы в некоторых случаях!
Джордж Грин
1
@ GeorgeGreen Вы можете получить доступ к себе. Отметьте переменную функции как ленивую и используйте список захвата внутри замыкания .
Заг
Только классы, протоколы, методы и свойства могут использовать @objc. Если вы используете параметр Enum в определении метода протокола @ objc, вы обречены.
Хуншань
1
@ Khunshan, этот метод не требует пометки @ objc, на что вы ссылаетесь?
Заг
Это информация по теме, которую перечисления нельзя использовать между swift и objc, которые для других операторов можно связать с ключевым словом @objc.
Хуншань
34

Вот конкретный пример с шаблоном делегирования.

Настройте свой протокол:

@objc protocol MyProtocol:class
{
    func requiredMethod()
    optional func optionalMethod()
}

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

    func callDelegate()
    {
        delegate?.requiredMethod()
        delegate?.optionalMethod?()
    }
}

Установите делегата для класса и реализуйте протокол. Смотрите, что необязательный метод не должен быть реализован.

class AnotherClass: NSObject, MyProtocol
{
    init()
    {
        super.init()

        let myInstance = MyClass()
        myInstance.delegate = self
    }

    func requiredMethod()
    {
    }
}

Одна важная вещь заключается в том, что дополнительный метод является необязательным и требует "?" при звонке. Упомяните второй знак вопроса.

delegate?.optionalMethod?()
Рафаэль
источник
2
Это должен быть правильный ответ. Просто, чисто и понятно.
Эшелон
я согласен, этот ответ короткий, сладкий и прямо к сути. Спасибо!
LuAndre
31

В Swift 3.0

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

Это сэкономит ваше время.

Гаутам Саририя
источник
6
Любой источник, почему @objcсейчас нужен всем членам?
DevAndArtist
@ Gautam Sareriya: Как вы думаете, что лучше всего подходит этот подход или создание пустых методов расширения?
эонист
Я пробовал с вашими методами с requiredфлагом, но с ошибками: requiredможет использоваться только в объявлениях 'init'.
Бен
27
  • Вам нужно добавить optional ключевое слово перед каждым методом.
  • Обратите внимание, однако, что для этого, ваш протокол должен быть помечен атрибутом @objc.
  • Это также подразумевает, что этот протокол будет применим к классам, но не к структурам.
Мехул Пармар
источник
Незначительная коррекция (слишком мала для редактирования!), Но она должна быть «необязательной», а не «@optional»
Али Бидл
5
Это также требует, чтобы вы отметили ваш класс, реализующий протокол, а @objcне только протокол.
Джонатон Саллингер
13

Чистый подход Swift с наследованием протокола:

//Required methods
protocol MyProtocol {
    func foo()
}

//Optional methods
protocol MyExtendedProtocol: MyProtocol {
    func bar()
}

class MyClass {
    var delegate: MyProtocol
    func myMethod() {
        (delegate as? MyExtendedProtocol).bar()
    }
}
guenis
источник
11

Чтобы проиллюстрировать механику ответа Антуана:

protocol SomeProtocol {
    func aMethod()
}

extension SomeProtocol {
    func aMethod() {
        print("extensionImplementation")
    }
}

class protocolImplementingObject: SomeProtocol {

}

class protocolImplementingMethodOverridingObject: SomeProtocol {
    func aMethod() {
        print("classImplementation")
    }
}

let noOverride = protocolImplementingObject()
let override = protocolImplementingMethodOverridingObject()

noOverride.aMethod() //prints "extensionImplementation"
override.aMethod() //prints "classImplementation"
eLillie
источник
8

Я думаю, что прежде чем спросить, как вы можете реализовать дополнительный метод протокола, вы должны спросить почему вы должны его реализовать.

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

Для дальнейшего чтения см. Https://useyourloaf.com/blog/swift-optional-protocol-methods/ , в котором содержится отличный обзор по этому вопросу.

ЕМЕМ
источник
Эта. Это следование принципу «Я» в принципах разработки программного обеспечения SOLID .
Эрик
7

Немного не по теме из первоначального вопроса, но он основывается на идее Антуана, и я подумал, что это может кому-то помочь.

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

Вы можете сделать свойство по протоколу необязательным

protocol SomeProtocol {
    var required: String { get }
    var optional: String? { get }
}

Реализовать фиктивное свойство в расширении протокола

extension SomeProtocol {
    var optional: String? { return nil }
}

И теперь вы можете использовать структуры, в которых реализовано или не реализовано необязательное свойство.

struct ConformsWithoutOptional {
    let required: String
}

struct ConformsWithOptional {
    let required: String
    let optional: String?
}

Я также написал в своем блоге , как настраивать необязательные свойства в протоколах Swift , и я буду постоянно обновлять их в случае изменений в выпусках Swift 2.

matthewpalmer
источник
5

Как создать необязательные и обязательные методы делегата.

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}
Саурабх Шарма
источник
3

Вот очень простой пример ТОЛЬКО для быстрых классов, а не для структур или перечислений. Обратите внимание, что метод протокола, являющийся необязательным, имеет два уровня необязательной цепочки при воспроизведении. Также классу, принимающему протокол, необходим атрибут @objc в своем объявлении.

@objc protocol CollectionOfDataDelegate{
   optional func indexDidChange(index: Int)
}


@objc class RootView: CollectionOfDataDelegate{

    var data = CollectionOfData()

   init(){
      data.delegate = self
      data.indexIsNow()
   }

  func indexDidChange(index: Int) {
      println("The index is currently: \(index)")
  }

}

class CollectionOfData{
    var index : Int?
    weak var delegate : CollectionOfDataDelegate?

   func indexIsNow(){
      index = 23
      delegate?.indexDidChange?(index!)
    }

 }
Благословение Лопес
источник
Вы можете описать немного больше « два уровня по желанию , в игре » часть, а именно это: delegate?.indexDidChange?(index!)?
Unheilig
2
Если бы мы написали в протоколе такой необязательный метод, как этот: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } тогда вы бы вызвали его без вопросительного знака: delegate?.indexDidChange(index!) когда вы устанавливаете необязательное требование для метода в протоколе, тип, который будет соответствовать ему, может НЕ реализовывать этот метод , поэтому ?используется для проверки реализации, и если ее нет, программа не будет аварийно завершена. @Unheilig
Благословение Лопес
weak var delegate : CollectionOfDataDelegate?(обеспечить слабую ссылку?)
Алфи Хансен
@BlessingLopes Можете ли вы добавить свое объяснение delegate?использования в свой ответ? Эта информация должна действительно принадлежать другим в будущем. Я хочу выразить это, но эта информация действительно должна быть в ответе.
Джонатон Саллингер
3

если вы хотите сделать это в чистом swift, лучшим способом будет предоставить реализацию по умолчанию, если вы возвращаете тип Swift, например, struct с типами Swift

пример :

struct magicDatas {
    var damagePoints : Int?
    var manaPoints : Int?
}

protocol magicCastDelegate {
    func castFire() -> magicDatas
    func castIce() -> magicDatas
}

extension magicCastDelegate {
    func castFire() -> magicDatas {
        return magicDatas()
    }

    func castIce() -> magicDatas {
        return magicDatas()
    }
}

тогда вы можете реализовать протокол без определения каждой функции

p1ckl3
источник
2

Существует два способа создания необязательного метода в быстром протоколе.

1 - Первый вариант - пометить ваш протокол с помощью атрибута @objc. Хотя это означает, что он может быть принят только классами, это означает, что вы помечаете отдельные методы как необязательные, например:

@objc protocol MyProtocol {
    @objc optional func optionalMethod()
}

2 - более быстрый способ: этот вариант лучше. Напишите реализации по умолчанию необязательных методов, которые ничего не делают, как это.

protocol MyProtocol {
    func optionalMethod()
    func notOptionalMethod()
}

extension MyProtocol {

    func optionalMethod() {
        //this is a empty implementation to allow this method to be optional
    }
}

Swift имеет функцию под названием extension, которая позволяет нам предоставлять реализацию по умолчанию для тех методов, которые мы хотим использовать не обязательно.

Himanshu
источник
0

Один из вариантов - сохранить их как необязательные переменные функции:

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
источник
-1

Определите функцию в протоколе и создайте расширение для этого протокола, затем создайте пустую реализацию для функции, которую вы хотите использовать как опциональную.

Ришу Гупта
источник
-2

Для определения Optional Protocolв swift вы должны использовать @objcключевое слово перед Protocolобъявлением и attribute/ methodобъявление внутри этого протокола. Ниже приведен пример необязательного свойства протокола.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Анкур Гупта
источник
2
Хотя это может ответить на вопрос, лучше добавить описание того, как этот ответ может помочь решить проблему. Пожалуйста, прочитайте Как я могу написать хороший ответ, чтобы узнать больше.
Рошана Питигала
-23

Поместите @optionalперед методами или свойствами.

shucao
источник
Ошибка компилятора: необязательный атрибут может применяться только к членам протокола @objc.
Селвин
3
@optionalдаже не правильное ключевое слово. Это так optional, и вы должны объявить класс и протокол с @objcатрибутом.
Джонатон Саллингер