Não - o método '@ objc' não satisfaz o requisito opcional do protocolo '@objc'

104

Visão geral:

  • Eu tenho um protocolo P1 que fornece uma implementação padrão de uma das funções opcionais do Objective-C.
  • Quando eu forneço uma implementação padrão da função opcional, há um aviso

Aviso do compilador:

Non-'@objc' method 'presentationController(_:viewControllerForAdaptivePresentationStyle:)' does not satisfy optional requirement of '@objc' protocol 'UIAdaptivePresentationControllerDelegate'

Versão:

  • Swift: 3
  • Xcode: 8 (lançamento público)

Tentativas feitas:

  • Tentei adicionar, @objcmas não ajudou

Questão:

  • Como faço para resolver isso?
  • Existe uma solução alternativa?

Código:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}


class A : UIViewController, P1 {

}
user1046037
fonte
Você tem a versão mais recente do Xcode? Eu não recebo nenhum erro se remover@objc
Qbyte de
Estou usando o Xcode 8 (versão pública mais recente). Não há erro, mas haverá um aviso
user1046037

Respostas:

183

Embora eu ache que posso responder à sua pergunta, não é uma resposta de que você goste.

TL; DR: as @objc funções podem não estar atualmente em extensões de protocolo. Em vez disso, você poderia criar uma classe base, embora essa não seja uma solução ideal.

Extensões de protocolo e Objective-C

Em primeiro lugar, esta pergunta / resposta ( Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c ) parece sugerir que, devido à forma como as extensões de protocolo são despachadas sob o capô, os métodos declarados em extensões de protocolo não são visíveis para a objc_msgSend()função, e portanto, não são visíveis para o código Objective-C. Uma vez que o método que você está tentando definir em sua extensão precisa ser visível para Objective-C (para que UIKitpossa usá-lo), ele grita com você para não incluir @objc, mas uma vez que você o inclui, ele grita com você porque @objcnão é permitido em extensões de protocolo. Provavelmente, isso ocorre porque as extensões de protocolo não podem ser visíveis para Objective-C no momento.

Também podemos ver que a mensagem de erro uma vez que adicionamos os @objcestados "@objc só pode ser usado com membros de classes, protocolos @objc e extensões concretas de classes." Esta não é uma aula; uma extensão para um protocolo @objc não é o mesmo que estar na própria definição de protocolo (ou seja, em requisitos), e a palavra "concreto" sugere que uma extensão de protocolo não conta como uma extensão de classe concreta.

Gambiarra

Infelizmente, isso impede completamente que você use extensões de protocolo quando as implementações padrão devem ser visíveis para estruturas Objective-C. No início, pensei que talvez @objcnão fosse permitido em sua extensão de protocolo porque o Compilador Swift não poderia garantir que os tipos em conformidade seriam classes (mesmo que você tenha especificado especificamente UIViewController). Então eu coloquei um classrequisito P1. Isso não funcionou.

Talvez a única solução alternativa seja simplesmente usar uma classe base em vez de um protocolo aqui, mas isso obviamente não é completamente ideal porque uma classe pode ter apenas uma única classe base, mas estar em conformidade com vários protocolos.

Se você escolher seguir esse caminho, leve esta questão ( Método de protocolo opcional Swift 3 ObjC não chamado na subclasse ) em consideração. Parece que outro problema atual no Swift 3 é que as subclasses não herdam automaticamente as implementações de requisitos de protocolo opcionais de sua superclasse. A resposta a essas perguntas usa uma adaptação especial de @objcpara contorná-la.

Relatando o problema

Acho que isso já está sendo discutido entre aqueles que trabalham nos projetos de código aberto do Swift, mas você pode ter certeza que eles estão cientes usando o Bug Reporter da Apple , que provavelmente acabaria chegando à equipe do Swift Core, ou o relator de bug do Swift . No entanto, qualquer um deles pode achar seu bug muito amplo ou já conhecido. A equipe do Swift também pode considerar o que você está procurando como um novo recurso de idioma; nesse caso, você deve primeiro verificar as listas de discussão .

Atualizar

Em dezembro de 2016, esse problema foi relatado à comunidade Swift. O problema ainda está marcado como aberto com prioridade média, mas o seguinte comentário foi adicionado:

Isso é intencional. Não há como adicionar a implementação do método a todos os adotantes, pois a extensão poderia ser adicionada após a conformidade com o protocolo. Suponho que poderíamos permitir se a extensão estiver no mesmo módulo que o protocolo.

Como seu protocolo está no mesmo módulo que sua extensão, no entanto, você poderá fazer isso em uma versão futura do Swift.

Atualização 2

Em fevereiro de 2017, esse problema foi oficialmente encerrado como "Não vou fazer" por um dos membros da equipe central da Swift com a seguinte mensagem:

Isso é intencional: as extensões de protocolo não podem introduzir pontos de entrada @objc devido às limitações do tempo de execução do Objective-C. Se você deseja adicionar pontos de entrada @objc a NSObject, estenda NSObject.

Estender NSObjectou mesmo UIViewControllernão vai realizar exatamente o que você deseja, mas infelizmente não parece que será possível.

Em um futuro de (muito) longo prazo, podemos ser capazes de eliminar @objctotalmente a dependência de métodos, mas esse momento provavelmente não chegará tão cedo, uma vez que as estruturas do Cocoa não são atualmente escritas em Swift (e não podem ser até que tenha uma ABI estável) .

Atualização 3

A partir do outono de 2019, isso está se tornando um problema menor porque mais e mais frameworks da Apple estão sendo escritos em Swift. Por exemplo, se você usar em SwiftUIvez de UIKit, contorna o problema totalmente porque @objcnunca seria necessário ao se referir a um SwiftUImétodo.

As estruturas da Apple escritas em Swift incluem:

  • SwiftUI
  • RealityKit
  • Combinar
  • CryptoKit

Seria de se esperar que esse padrão continuasse ao longo do tempo, agora que o Swift é oficialmente ABI e módulo estável a partir do Swift 5.0 e 5.1, respectivamente.

Matthew Seaman
fonte
1
@ user1046037 Eu também, pois posso me ver enfrentando esse problema muitas vezes em desenvolvimentos futuros.
Matthew Seaman,
2
Você está correto, sua resposta ainda é válida, apesar de Swift 4não haver outra alternativa no momento.
user1046037
1
Eu tinha exatamente o mesmo código funcionando para mim por um tempo, mas ainda assim quebrando em uma versão posterior do Xcode. Isso é muito irritante. Existem tantos métodos opcionais em protocolos Objective-C.
Departamento B de
0

Acabei de encontrar isso depois de habilitar 'estabilidade de módulo' (ativando 'Construir bibliotecas para distribuição') em uma estrutura rápida que uso.

O que eu tinha era algo assim:

class AwesomeClass: LessAwesomeClass {
...
}

extension AwesomeClass: GreatDelegate {
  func niceDelegateFunc() {
  }
}

A função na extensão apresentou estes erros:

  • O método de instância '@objc' na extensão da subclasse de 'LessAwesomeClass' requer iOS 13.0.0

  • Não - método '@ objc' 'niceDelegateFunc' não satisfaz o requisito do protocolo '@objc' 'GreatDelegate'

Mover as funções para a classe em vez de para uma extensão resolveu o problema.

CMash
fonte
0

Aqui está outra solução alternativa. Eu também encontrei esse problema e não consigo mudar do UIKit para o SwiftUI ainda. Mover as implementações padrão para uma classe base comum também não era uma opção para mim. Minhas implementações padrão eram bastante extensas, então eu realmente não queria ter todo aquele código duplicado. A solução alternativa que acabei usando foi usar funções de wrapper no protocolo e, em seguida, simplesmente chamar essas funções de cada classe. Não é bonito, mas pode ser melhor que a alternativa, dependendo da situação. Seu código ficaria assim:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {
}

extension P1 where Self : UIViewController {
    func wrapPresentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}

class A : UIViewController, P1 {
    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return wrapPresentationController(controller, viewControllerForAdaptivePresentationStyle: style)
    }
}
René
fonte