Como posso lidar com a depreciação de inferência @objc com #selector () no Swift 4?

131

Estou tentando converter o código-fonte do meu projeto de Swift 3 para Swift 4. Um aviso que o Xcode está me dando é sobre meus seletores.

Por exemplo, adiciono um destino a um botão usando um seletor regular como este:

button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)

Este é o aviso que mostra:

O argumento '#selector' refere-se ao método de instância 'myAction ()' em 'ViewController' que depende da inferência de atributo '@objc' descontinuada no Swift 4

Adicione '@objc' para expor esse método de instância ao Objective-C

Agora, bater Fixna mensagem de erro faz isso com a minha função:

// before
func myAction() { /* ... */ }

// after
@objc func myAction() { /* ... */ }

Eu realmente não quero renomear todas as minhas funções para incluir a @objcmarca e estou assumindo que isso não é necessário.

Como reescrevo o seletor para lidar com a depreciação?


Pergunta relacionada:

LinusGeffarth
fonte
3
Não, marcando-os como @objc é agora necessária, de modo a expô-los a Obj-C, e, portanto, usar-se com selectores.
217 Hamish
3
Portanto, a parte preterida está deduzindo funções de acesso público como @objc? Isso é um pouco chato, mas geralmente faço essas funções privadas, exigindo que eu a marque de @objcqualquer maneira.
Connor
2
Consulte o SE-0160 para obter mais informações sobre a alteração. Outra alternativa é marcar sua classe como @objcMemberspara expor todos os membros compatíveis com Obj-C ao Obj-C, mas eu não recomendaria isso, a menos que você realmente precise que toda a sua classe seja exposta.
217 Hamish
3
@LinusGeffarth Como dito na proposta, provavelmente aumentaria desnecessariamente o tamanho da sua vinculação binária e dinâmica levaria mais tempo. Eu realmente não acho que seja muito aborrecido pela clareza adicional que você quer dizer especificamente que uma coisa específica seja usada no Obj-C.
217 Hamish
1
Tentei, não está funcionando.
LinusGeffarth

Respostas:

156

A correção está correta - não há nada sobre o seletor que você possa alterar para tornar o método ao qual ele se refere exposto ao Objective-C.

Todo o motivo desse aviso é o resultado do SE-0160 . Antes do Swift 4, internaldeduzia-se que membros compatíveis com Objective-C ou superior de NSObjectclasses herdadas eram@objc e expostos ao Objective-C, permitindo que fossem chamados usando seletores (como o tempo de execução do Obj-C é necessário para procurar o método implementação para um determinado seletor).

No entanto, no Swift 4, esse não é mais o caso. Agora, deduz-se apenas que declarações muito específicas são @objc, por exemplo, substituições de @objcmétodos, implementações de @objcrequisitos de protocolo e declarações com atributos que implicam @objc, como @IBOutlet.

A motivação por trás disso, conforme detalhado na proposta vinculada acima , é primeiramente evitar sobrecargas de método emNSObject classes herdadas colidam entre si devido a terem seletores idênticos. Em segundo lugar, ajuda a reduzir o tamanho binário por não precisar gerar thunks para membros que não precisam ser expostos ao Obj-C e, por outro lado, melhora a velocidade da vinculação dinâmica.

Se você deseja expor um membro ao Obj-C, é necessário marcá-lo como @objc, por exemplo:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        button.addTarget(self, action: #selector(foo), for: .touchUpInside)
    }

    @objc func foo() {
       // ... 
    }
}

(o migrante deve fazer isso automaticamente para você com seletores ao executar com a opção "minimizar inferência" selecionada)

Para expor um grupo de membros ao Obj-C, você pode usar um @objc extension:

@objc extension ViewController {

    // both exposed to Obj-C
    func foo() {}
    func bar() {}
}

Isso expõe todos os membros definidos a ele ao Obj-C e gera um erro em qualquer membro que não possa ser exposto ao Obj-C (a menos que seja explicitamente marcado como @nonobjc).

Se você tem uma classe na qual você precisa que todos os membros compatíveis com Obj-C sejam expostos ao Obj-C, é possível marcar a classe como @objcMembers:

@objcMembers
class ViewController: UIViewController {
   // ...
}

Agora, todos os membros que podem ser deduzidos @objcserão. No entanto, eu não recomendaria fazer isso, a menos que você realmente precise de todos os membros expostos ao Obj-C, dadas as desvantagens acima mencionadas de ter membros desnecessariamente expostos.

Hamish
fonte
2
Por que o Xcode não faz isso automaticamente ao converter o código para a sintaxe mais recente?
LinusGeffarth
1
Gostaria de saber, @IBActiontambém são automaticamente @objc? esse não era o caso até agora, mas seria lógico. EDIT: nvm, a proposta afirma claramente que @IBActioné suficiente para um método ser @objc.
Sulthan
8
Eu não entendo nada disso. Eu não tenho nenhum código Objective-C que seja. Não existe uma maneira rápida de adicionar alvos a um botão? Ou usar seletores? Não quero que meu código seja repleto de atributos. É porque um UIButton é derivado de alguma coisa NSObject / Obj-c-thing?
Sti
11
@Sti Então, para responder diretamente " Não existe uma maneira rápida de adicionar alvos a um botão? Ou usar seletores? " - não, não há uma maneira "pura pura" de usar seletores para enviar aos métodos. Eles contam com o tempo de execução do Obj-C para procurar a implementação do método para solicitar um seletor específico - o tempo de execução Swift não tem esse recurso.
Hamish
12
Os seletores de homem estão uma bagunça. Parece que todas as outras atualizações rápidas estão mexendo com ela. Por que não podemos simplesmente ter um seletor de preenchimento automático rápido e puro para métodos. Faz o código parecer tão feio.
John Riselvato 10/10
16

Como documentação oficial da Apple . você precisa usar @objc para chamar seu método seletor.

No Objective-C, um seletor é um tipo que se refere ao nome de um método Objective-C. No Swift, os seletores de Objective-C são representados pela Selectorestrutura e podem ser construídos usando o#selector expressão Para criar um seletor para um método que pode ser chamado de Objective-C, passe o nome do método, como #selector(MyViewController.tappedButton(sender:)). Para construir um selector para o objectivo C-absorvente de uma propriedade ou método de ajuste, passar o nome da propriedade prefixado pelo getter:ou setter:marcador, tal como #selector(getter: MyViewController.myButton).

Kiran Sarvaiya
fonte
tudo o que entendo disso é que, em alguns casos, preciso adicionar @objc ao início de uma função, quer eu chame ou não de Objective-C. Estou aprendendo Swift ter usado Obj-C durante anos
SundialSoft
10

A partir de, acho que o Swift 4.2, tudo que você precisa fazer é atribuir @IBAction ao seu método e você pode evitar essa anotação boba do @objc

`` ``

let tap  =  UITapGestureRecognizer(target: self, action: #selector(self.cancel))


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}
Logan Sease
fonte
1
Isso também informará ao construtor de interface que esta função pode ser conectada, o que pode não ser o que você deseja. Também está fazendo a mesma coisa que @objc.
Josh Paradroid
2

Como já mencionado em outras respostas, não há como evitar a @objcanotação para seletores.

Porém, o aviso mencionado no OP pode ser silenciado executando as seguintes etapas:

  1. Vá para Configurações de compilação
  2. Pesquise a palavra-chave @objc
  3. Defina o valor da interface Swift 3 @objc comoOff

abaixo está a captura de tela que ilustra as etapas mencionadas acima:

Silenciando o aviso "Swift 3 @objc interface"

Espero que isto ajude

S1LENT WARRIOR
fonte
Como isso afeta o seu projeto no quadro geral?
Zonily Jame
1
Que tal o tamanho * .ipa ou * .xarchive e o tempo de construção?
Zonily Jame
@ZonilyJame i não percebeu o tempo de compilação, mas não houve efeito do tamanho do IPA
S1LENT WARRIOR
Esse valor não é recomendado, você pode usar se usar a versão rápida 4.0+, conforme as diretrizes da Apple. Verifique esta ajuda.apple.com/xcode/mac/current/#/deve838b19a1
Bhavin_m
1
Você pode atualizar esta resposta definindo o valor como Padrão no Xcode 11. É importante definir o valor no projeto e nos seus destinos para remover completamente o aviso.
Tommie C.
2

Se você precisar de membros c objetivos no seu controlador de exibição, adicione @objcMembers na parte superior do controlador de exibição. E você pode evitar isso adicionando IBAction no seu código.

@IBAction func buttonAction() {

}

Certifique-se de conectar esta tomada no storyboard.

Renovar C
fonte