Como definir métodos opcionais no protocolo Swift?

359

É possível em Swift? Caso contrário, existe uma solução alternativa para fazer isso?

Selvin
fonte
11
Eu finalmente me motivei a atualizar minha resposta, você pode reconsiderar a aceitação.
akashivskyy 7/05/19
@akashivskyy Aceito sua resposta, pois mostra melhor todas as opções disponíveis e seus prós e contras.
Selvin

Respostas:

505

1. Utilizando implementações padrão (preferencial).

protocol MyProtocol {
    func doSomething()
}

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

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

Vantagens

  • Nenhum tempo de execução do Objective-C está envolvido (bem, pelo menos não explicitamente). Isso significa que você pode conformar estruturas, enumerações e não NSObjectclasses a ele. Além disso, isso significa que você pode tirar proveito do poderoso sistema de genéricos.

  • Você sempre pode ter certeza de que todos os requisitos sejam atendidos ao encontrar tipos que estejam em conformidade com esse protocolo. É sempre uma implementação concreta ou padrão. É assim que "interfaces" ou "contratos" se comportam em outros idiomas.

Desvantagens

  • Para não- Voidrequisitos, você precisa ter um valor padrão razoável , o que nem sempre é possível. No entanto, quando você encontra esse problema, significa que esse requisito realmente não deve ter uma implementação padrão ou que você cometeu um erro durante o design da API.

  • Você não pode distinguir entre uma implementação padrão e nenhuma implementação , pelo menos sem resolver esse problema com valores de retorno especiais. Considere o seguinte exemplo:

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

    Se você fornecer uma implementação padrão que apenas retorna true- tudo bem à primeira vista. Agora, considere o seguinte pseudo-código:

    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 */
            }
        }
    }

    Não há como implementar essa otimização - você não pode saber se seu delegado implementa um método ou não.

    Embora haja várias maneiras diferentes de superar esse problema (usando fechamentos opcionais, objetos delegados diferentes para operações diferentes, entre outros), esse exemplo apresenta o problema claramente.


2. Usando @objc optional.

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

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

Vantagens

  • Nenhuma implementação padrão é necessária. Você acabou de declarar um método opcional ou uma variável e está pronto para começar.

Desvantagens

  • Isso limita severamente os recursos do seu protocolo, exigindo que todos os tipos em conformidade sejam compatíveis com Objective-C. Isso significa que apenas as classes herdadas NSObjectpodem estar em conformidade com esse protocolo. Sem estruturas, sem enumerações, sem tipos associados.

  • Você sempre deve verificar se um método opcional é implementado , opcionalmente, chamando ou verificando se o tipo conforme o implementa. Isso pode introduzir muitos clichês se você estiver chamando métodos opcionais com frequência.

akashivskyy
fonte
17
Olhe para um melhor maneira de métodos opcionais em rápida usando uma extensão (abaixo)
Daniel Kanaan
11
E como testar um suporte a um método de protocolo opcional em uma instância? respondsToSelector?
devios1
3
Só não faça isso! Swift não suporta método opcional em protocolos por um motivo.
fpg1503
2
Este método não suporta opcionais em parâmetros. Ou seja, você não pode fazer issooptional func doSomething(param: Int?)
SoftDesigner
4
Certamente esta resposta está essencialmente errada hoje em dia, anos depois. Hoje, no Swift, você simplesmente adiciona uma extensão para a implementação padrão, é um aspecto básico do Swift. (Como todas as respostas modernas abaixo mostram.) Seria errado adicionar a bandeira objc hoje em dia.
Fattie
394

No Swift 2 e em diante, é possível adicionar implementações padrão de um protocolo. Isso cria uma nova maneira de métodos opcionais em protocolos.

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

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

Não é uma maneira muito boa de criar métodos opcionais de protocolo, mas oferece a possibilidade de usar estruturas em retornos de chamada de protocolo.

Eu escrevi um pequeno resumo aqui: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
fonte
2
Esta é provavelmente a maneira mais limpa de fazer isso no Swift. Pena que não funciona antes do Swift 2.0.
Entalpi
13
@MattQuiros Estou descobrindo que você realmente precisa declarar a função na definição de protocolo, caso contrário, a função de extensão sem operação não será substituída em suas classes que estejam em conformidade com o protocolo.
30615 Ian
3
@IanPearce está correto, e isso parece ser intencional. Na palestra "Programação Orientada a Protocolo" (408) da WWDC, eles falam sobre métodos no protocolo principal como "pontos de personalização" oferecidos a tipos conformes. Um ponto de personalização necessário não recebe uma definição em uma extensão; um ponto opcional sim. Os métodos no protocolo que geralmente não devem ser personalizados são totalmente declarados / definidos na extensão para desabilitar os tipos conformes a serem personalizados, a menos que você faça uma conversão específica para seu dynamicType para mostrar que deseja a implementação personalizada do conformista.
Matthias
4
@FranklinYu Você pode fazer isso, mas depois polui seu design de API com 'lances', onde na verdade não é necessário. Eu gosto mais da ideia de "micro protocolos". Por exemplo, cada método confirma a um protocolo e, em seguida, você pode verificar: se o objeto é um protocolo
Darko
3
@ Antonine Acho que você deve tornar pública a função na extensão, pois as funções nos protocolos são públicas por definição. Sua solução não funcionará quando o protocolo for usado fora de seu módulo.
Vadim Eisenberg
39

Como existem algumas respostas sobre como usar o modificador opcional e o atributo @objc para definir o protocolo de requisitos opcional, darei uma amostra sobre como usar extensões de protocolo para definir o protocolo opcional.

O código abaixo é o 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*

Observe que os métodos de extensão de protocolo não podem ser invocados pelo código Objective-C e, pior ainda, a equipe Swift não o corrigirá. https://bugs.swift.org/browse/SR-492

Eddie.Dou
fonte
11
Bom trabalho! Essa deve ser a resposta correta, pois resolve o problema sem envolver o tempo de execução do Objective-C.
Lukas
muito legal!
tania_S
da documentação rápida - "Os requisitos de protocolo com implementações padrão fornecidas por extensões são distintos dos requisitos de protocolo opcionais. Embora os tipos em conformidade não precisem fornecer sua própria implementação, os requisitos com implementações padrão podem ser chamados sem encadeamento opcional".
Mec Os
34

As outras respostas aqui envolvendo a marcação do protocolo como "@objc" não funcionam ao usar tipos específicos rápidos.

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"

Para declarar protocolos opcionais que funcionam bem com o swift, declare as funções como variáveis ​​em vez de func.

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

E, em seguida, implemente o protocolo da seguinte maneira

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)?
}

Você pode usar "?" para verificar se a função foi ou não implementada

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

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
fonte
3
Com esta solução, você não pode acessar a si próprio de nenhuma das funções do protocolo. Isso pode causar problemas em alguns casos!
George Green
11
@GeorgeGreen Você pode acessar a si próprio. Marque a variável de função como lenta e use uma lista de captura dentro do fechamento .
Zag
Somente classes, protocolos, métodos e propriedades podem usar @objc. Caso você esteja usando um parâmetro Enum na definição do método do protocolo @ objc, você estará condenado.
khunshan
11
@khunshan, este método não requer nada sendo marcado como @ objc, a que você está se referindo?
Zag
é uma informação sobre o tópico que enums não pode ser usado entre swift e objc, que as outras instruções podem ser conectadas com a palavra-chave @objc.
precisa saber é o seguinte
34

Aqui está um exemplo concreto com o padrão de delegação.

Configure seu protocolo:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Defina o delegado para uma classe e implemente o protocolo. Veja que o método opcional não precisa ser implementado.

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

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

    func requiredMethod()
    {
    }
}

Uma coisa importante é que o método opcional é opcional e precisa de um "?" ao ligar. Mencione o segundo ponto de interrogação.

delegate?.optionalMethod?()
Rafael
fonte
2
Essa deve ser a resposta correta. Simples, limpo e explicativo.
Echelon
Eu concordo, esta resposta é curta, doce e direta ao ponto. obrigado!
LuAndre
31

No Swift 3.0

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

Isso economizará seu tempo.

Gautam Sareriya
fonte
6
Alguma fonte do porquê @objcé necessária em todos os membros agora?
DevAndArtist
@ Gautam Sareriya: O que você acha que é melhor essa abordagem ou a criação de métodos de extensão vazios?
eonist
Eu tentei com seus métodos com requiredflag, mas com erros: requiredsó pode ser usado em declarações 'init'.
Ben
27
  • Você precisa adicionar uma optionalpalavra-chave antes de cada método.
  • Observe, no entanto, que, para que isso funcione, seu protocolo deve ser marcado com o atributo @objc.
  • Isso implica ainda que esse protocolo seria aplicável a classes, mas não a estruturas.
Mehul Parmar
fonte
Pequena correcção (muito pequeno para editar!), Mas deve ser 'opcional' não '@optional'
Ali Beadle
5
Isso também exige que você marque sua classe implementando o protocolo como @objc, não apenas o protocolo.
Johnathon Sullinger
13

Uma abordagem Swift pura com herança de protocolo:

//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
fonte
11

Para ilustrar a mecânica da resposta de Antoine:

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
fonte
8

Penso que antes de perguntar como você pode implementar um método de protocolo opcional, você deve estar se perguntando por que deveria implementar um.

Se pensarmos em protocolos rápidos como uma interface na programação clássica orientada a objetos, métodos opcionais não fazem muito sentido, e talvez uma solução melhor seria criar implementação padrão ou separar o protocolo em um conjunto de protocolos (talvez com algumas relações de herança entre eles) para representar a possível combinação de métodos no protocolo.

Para uma leitura mais detalhada, consulte https://useyourloaf.com/blog/swift-optional-protocol-methods/ , que fornece uma excelente visão geral sobre esse assunto.

emem
fonte
Este. É aderência ao princípio "I" nos princípios de desenvolvimento de software do SOLID .
Eric
7

Um pouco fora do tópico da pergunta original, mas parte da ideia de Antoine e achei que poderia ajudar alguém.

Você também pode tornar as propriedades computadas opcionais para estruturas com extensões de protocolo.

Você pode tornar uma propriedade no protocolo opcional

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

Implemente a propriedade computada fictícia na extensão de protocolo

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

E agora você pode usar estruturas que possuem ou não a propriedade opcional implementada

struct ConformsWithoutOptional {
    let required: String
}

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

Também escrevi como fazer propriedades opcionais nos protocolos Swift no meu blog , que vou manter atualizado caso as coisas mudem com os lançamentos do Swift 2.

matthewpalmer
fonte
5

Como criar métodos delegados opcionais e necessários.

@objc protocol InterViewDelegate:class {

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

}
Saurabh Sharma
fonte
3

Aqui está um exemplo muito simples para Classes rápidas, e não para estruturas ou enumerações. Observe que o método do protocolo, sendo opcional, possui dois níveis de encadeamento opcional em ação. Além disso, a classe que adota o protocolo precisa do atributo @objc em sua declaração.

@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!)
    }

 }
Bênção Lopes
fonte
Você pode descrever um pouco mais a parte " dois níveis opcionais em jogo ", a saber delegate?.indexDidChange?(index!):?
Unheilig
2
Se tivéssemos escrito o protocolo para ter um método não opcional como este: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } então você o chamaria sem o ponto de interrogação: delegate?.indexDidChange(index!) Quando você define um requisito opcional para um método em um protocolo, o Tipo que se conforma a ele NÃO pode implementar esse método , então ?é usado para verificar a implementação e, se não houver, o programa não falhará. @Unheilig
Blessing Lopes
weak var delegate : CollectionOfDataDelegate?(assegurar referência fraca?)
Alfie Hanssen
@BlessingLopes Você pode adicionar sua explicação sobre o delegate?uso à sua resposta? Essa informação deve realmente pertencer a outras pessoas no futuro. Eu quero aprovar isso, mas essa informação deve realmente estar na resposta.
Johnathon Sullinger
3

se você quiser fazê-lo em puro swift, a melhor maneira é fornecer uma partição padrão de implementação se você retornar um tipo Swift, como por exemplo struct com tipos Swift

exemplo:

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()
    }
}

então você pode implementar o protocolo sem definir todas as funções

p1ckl3
fonte
2

Existem duas maneiras de criar um método opcional no protocolo rápido.

1 - A primeira opção é marcar o seu protocolo usando o atributo @objc. Enquanto isso significa que ele pode ser adotado apenas por classes, significa que você marca métodos individuais como opcionais como este:

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

2 - Uma maneira mais rápida: Esta opção é melhor. Escreva implementações padrão dos métodos opcionais que não fazem nada, como este.

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

extension MyProtocol {

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

O Swift possui um recurso chamado extension que nos permite fornecer uma implementação padrão para os métodos que queremos que sejam opcionais.

Himanshu
fonte
0

Uma opção é armazená-los como variáveis ​​de função opcionais:

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

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
fonte
-1

Defina a função no protocolo e crie extensão para esse protocolo e crie uma implementação vazia para a função que você deseja usar como opcional.

Rishu Gupta
fonte
-2

Para definir Optional Protocolrapidamente, você deve usar a @objcpalavra-chave antes da Protocoldeclaração e attribute/ methoddeclaração dentro desse protocolo. Abaixo está uma amostra da propriedade opcional de um protocolo.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Ankur Gupta
fonte
2
Embora isso possa responder à pergunta, é melhor adicionar uma descrição de como essa resposta pode ajudar a resolver o problema. Por favor, leia Como escrevo uma boa resposta para saber mais.
Roshana Pitigala
-23

Coloque @optionalna frente de métodos ou propriedades.

shucao
fonte
Erro do compilador: o atributo 'opcional' pode ser aplicado apenas a membros de um protocolo @objc.
Selvin
3
@optionalnem sequer é a palavra-chave correta. É optional, e você deve declarar a classe e o protocolo com o @objcatributo
Johnathon Sullinger