Come posso gestire la deprecazione di inferenza di @objc con #selector () in Swift 4?


131

Sto cercando di convertire il codice sorgente del mio progetto da Swift 3 a Swift 4. Un avvertimento che Xcode mi sta dando riguarda i miei selettori.

Ad esempio, aggiungo una destinazione a un pulsante usando un selettore normale come questo:

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

Questo è l'avvertimento che mostra:

L'argomento di '#selector' si riferisce al metodo di istanza 'myAction ()' in 'ViewController' che dipende dall'inferenza dell'attributo '@objc' deprecata in Swift 4

Aggiungi "@objc" per esporre questo metodo di istanza a Objective-C

Ora, premere Fixsul messaggio di errore fa questo alla mia funzione:

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

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

Non voglio davvero rinominare tutte le mie funzioni per includere il @objcsegno e presumo che non sia necessario.

Come riscrivo il selettore per gestire la deprecazione?


Domanda correlata:


3
No, contrassegnandoli come @objc è ora necessario per esporli a Obj-C, e quindi utilizzare con i selettori.
Hamish,

3
Quindi la parte deprecata sta inferendo le funzioni di accesso pubblico come @objc? È un po 'fastidioso, ma in genere rendo private queste funzioni, richiedendomi di contrassegnarlo come @objccomunque.
Connor,

2
Vedi SE-0160 per maggiori informazioni sulla modifica. Un'altra alternativa è contrassegnare la tua data classe come @objcMembersper esporre tutti i membri compatibili con Obj-C a Obj-C, ma non lo consiglierei a meno che tu non abbia effettivamente bisogno che tutta la tua classe sia esposta.
Hamish,

3
@LinusGeffarth Come detto nella proposta, probabilmente aumenterebbe inutilmente la dimensione del tuo binario e il collegamento dinamico richiederebbe più tempo. Non credo davvero che sia troppo seccante per la maggiore chiarezza che intendi specificamente per una cosa particolare da usare da Obj-C.
Hamish,

1
Ho provato, non funziona.
LinusGeffarth,

Risposte:


156

La correzione è corretta: non c'è nulla nel selettore che puoi modificare per rendere il metodo a cui si riferisce esposto a Objective-C.

L'intero motivo di questo avviso è in primo luogo il risultato di SE-0160 . Prima di Swift 4, si internalriteneva che i membri compatibili delle NSObjectclassi ereditarie di Objective-C o superiori fossero @objcesposti e quindi esposti a Objective-C, consentendo quindi di essere chiamati mediante selettori (poiché è necessario il runtime Obj-C per cercare il metodo implementazione per un determinato selettore).

Tuttavia, in Swift 4, non è più così. Si deduce ora che solo dichiarazioni molto specifiche sono @objc, ad esempio, sostituzioni di @objcmetodi, implementazioni di @objcrequisiti di protocollo e dichiarazioni con attributi che implicano @objc, come ad esempio @IBOutlet.

La motivazione alla base di ciò, come spiegato nella proposta sopra collegata , è innanzitutto quella di evitare che i sovraccarichi di metodo NSObjectnell'ereditare le classi si scontrino a causa della presenza di selettori identici. In secondo luogo, aiuta a ridurre le dimensioni binarie non dovendo generare thunk per i membri che non devono essere esposti a Obj-C e in terzo luogo migliora la velocità del collegamento dinamico.

Se si desidera esporre un membro a Obj-C, è necessario contrassegnarlo come @objc, ad esempio:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

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

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

(il migratore dovrebbe farlo automaticamente con i selettori quando è in esecuzione con l'opzione "minimizza inferenza" selezionata)

Per esporre un gruppo di membri a Obj-C, puoi usare un @objc extension:

@objc extension ViewController {

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

Ciò esporrà a Obj-C tutti i membri definiti in esso e genererà un errore su tutti i membri che non possono essere esposti a Obj-C (a meno che non siano esplicitamente contrassegnati come @nonobjc).

Se hai una classe in cui hai bisogno che tutti i membri compatibili con Obj-C siano esposti a Obj-C, puoi contrassegnare la classe come @objcMembers:

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

Ora, lo saranno tutti i membri che si può dedurre @objc. Tuttavia, non consiglierei di farlo a meno che tu non abbia davvero bisogno di tutti i membri esposti all'Obj-C, dati gli svantaggi sopra menzionati di avere membri inutilmente esposti.


2
Perché Xcode non lo fa automaticamente quando si converte il codice nell'ultima sintassi?
LinusGeffarth,

1
Mi chiedo, sono @IBActionanche automaticamente @objc? fino ad ora non era così, ma sarebbe logico. EDIT: nvm, la proposta afferma chiaramente che @IBActionè sufficiente per essere un metodo @objc.
Sulthan,

8
Non capisco niente di tutto questo. Non ho un codice C oggettivo che mai. Non esiste un modo Swift puro per aggiungere obiettivi a un pulsante? O utilizzare i selettori? Non voglio che il mio codice sia pieno di questo attributo. È perché un UIButton è derivato da qualche cosa NSObject / Obj-c?
Sti,

11
@Sti Quindi, per rispondere direttamente " Non esiste un modo Swift puro di aggiungere obiettivi a un pulsante? O utilizzare i selettori? " - no, non esiste un modo "puro Swift" di utilizzare i selettori per inviare metodi. Si basano sul runtime Obj-C per cercare l'implementazione del metodo in modo da richiedere un particolare selettore: il runtime Swift non ha questa capacità.
Hamish,

12
I selettori man sono un casino. Sembra che ogni altro rapido aggiornamento lo stiano rovinando. Perché non possiamo semplicemente avere un selettore di completamento automatico rapido per i metodi. Rende il codice così brutto.
John Riselvato,

16

Come documentazione ufficiale Apple . devi usare @objc per chiamare il tuo metodo di selezione.

In Objective-C, un selettore è un tipo che fa riferimento al nome di un metodo Objective-C. In Swift, i selettori Objective-C sono rappresentati dalla Selectorstruttura e possono essere costruiti usando l' #selector espressione. Per creare un selettore per un metodo che può essere chiamato da Objective-C, passare il nome del metodo, ad esempio #selector(MyViewController.tappedButton(sender:)). Per costruire un selettore per il metodo getter o setter Objective-C di una proprietà, passare il nome della proprietà preceduto dall'etichetta getter:o setter:, ad esempio #selector(getter: MyViewController.myButton).


tutto ciò che capisco è che in alcuni casi devo aggiungere @objc all'inizio di una funzione, indipendentemente dal fatto che lo chiamerò o meno da Objective-C. Sto solo imparando che Swift usa Obj-C da anni
SundialSoft il

10

A partire da, penso Swift 4.2, tutto ciò che devi fare è assegnare @IBAction al tuo metodo e puoi evitare questa stupida annotazione @objc

`` `

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


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}

1
Questo dirà anche al costruttore di interfacce che questa funzione può essere connessa, che potrebbe non essere quella desiderata. Fa anche la stessa cosa di @objc.
Josh Paradroid,

2

Come già menzionato in altre risposte, non c'è modo di evitare l' @objcannotazione per i selettori.

Ma gli avvisi menzionati nel PO possono essere messi a tacere seguendo i seguenti passi:

  1. Vai a Impostazioni di costruzione
  2. Cerca la parola chiave @objc
  3. Impostare il valore dell'interfaccia Swift 3 @objc suOff

di seguito è riportato lo screenshot che illustra i passaggi sopra menzionati:

Disattivazione dell'avviso "Interfaccia Swift 3 @objc"

Spero che questo ti aiuti


In che modo ciò influisce sul tuo progetto nel quadro più ampio?
Zonily Jame,

1
Che ne dici di * .ipa o * .xarchive size e tempo di costruzione?
Zonily Jame,

@ZonilyJame non ho notato il tempo di compilazione, ma non vi è stato alcun effetto sulla dimensione IPA
S1LENT WARRIOR il

Questo non è un valore raccomandato che puoi usare se utilizzi la versione 4.0+ di swift secondo le linee guida Apple. Controlla questo help.apple.com/xcode/mac/current/#/deve838b19a1
Bhavin_m

1
Puoi aggiornare questa risposta impostando il valore su Predefinito in Xcode 11. È importante impostare il valore sia sul progetto che sugli obiettivi per rimuovere completamente l'avviso.
Tommie C.,

2

Se hai bisogno di membri obiettivo c nel controller della vista, aggiungi semplicemente @objcMembers nella parte superiore del controller della vista. E puoi evitarlo aggiungendo IBAction nel tuo codice.

@IBAction func buttonAction() {

}

Assicurati di collegare questa presa nello storyboard.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.