Comment puis-je gérer la dépréciation de l'inférence @objc avec #selector () dans Swift 4?

131

J'essaie de convertir le code source de mon projet de Swift 3 à Swift 4. Un avertissement que Xcode me donne concerne mes sélecteurs.

Par exemple, j'ajoute une cible à un bouton en utilisant un sélecteur régulier comme celui-ci:

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

Voici l'avertissement qu'il affiche:

L'argument de '#selector' fait référence à la méthode d'instance 'myAction ()' dans 'ViewController' qui dépend de l'inférence d'attribut '@objc' obsolète dans Swift 4

Ajoutez '@objc' pour exposer cette méthode d'instance à Objective-C

Maintenant, appuyer Fixsur le message d'erreur fait ceci à ma fonction:

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

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

Je ne veux pas vraiment renommer toutes mes fonctions pour inclure la @objcmarque et je suppose que ce n'est pas nécessaire.

Comment réécrire le sélecteur pour gérer la dépréciation?


Question connexe:

LinusGeffarth
la source
3
Non, les marquer comme il @objc est maintenant nécessaire pour les exposer à Obj-C, et donc les utiliser avec des sélecteurs.
Hamish
3
Donc, la partie obsolète infère des fonctions d'accès public comme @objc? C'est un peu ennuyeux, mais je rend généralement ces fonctions privées, ce qui m'oblige à le marquer de @objctoute façon.
Connor
2
Voir SE-0160 pour plus d'informations sur le changement. Une autre alternative consiste à marquer votre classe donnée comme @objcMembersafin d'exposer tous les membres compatibles Obj-C à Obj-C, mais je ne le conseillerais pas à moins que vous n'ayez réellement besoin que votre classe entière soit exposée.
Hamish
3
@LinusGeffarth Comme indiqué dans la proposition, cela augmenterait probablement inutilement la taille de votre liaison binaire et la liaison dynamique prendrait plus de temps. Je ne pense vraiment pas que ce soit trop compliqué pour la clarté supplémentaire que vous entendez spécifiquement pour une chose particulière à utiliser à partir d'Obj-C.
Hamish
1
Essayé, ne fonctionne pas.
LinusGeffarth

Réponses:

156

Le correctif est correct - il n'y a rien dans le sélecteur que vous pouvez changer pour que la méthode à laquelle il se réfère soit exposée à Objective-C.

La raison de cet avertissement en premier lieu est le résultat du SE-0160 . Avant Swift 4, internalou les membres compatibles Objective-C ou supérieurs des NSObjectclasses héritières étaient déduits comme étant @objcet donc exposés à Objective-C, leur permettant ainsi d'être appelés à l'aide de sélecteurs (car le runtime Obj-C est nécessaire pour rechercher la méthode implémentation pour un sélecteur donné).

Cependant dans Swift 4, ce n'est plus le cas. Seules les déclarations très spécifiques sont désormais supposées être @objc, par exemple, des substitutions de @objcméthodes, des implémentations d' @objcexigences de protocole et des déclarations avec des attributs qui impliquent @objc, tels que @IBOutlet.

La motivation derrière cela, comme détaillé dans la proposition liée ci-dessus , est tout d'abord d'éviter que les surcharges de méthodes lors de l' NSObjecthéritage de classes ne se heurtent en raison de sélecteurs identiques. Deuxièmement, cela aide à réduire la taille binaire en n'ayant pas à générer des thunks pour les membres qui n'ont pas besoin d'être exposés à Obj-C, et troisièmement, améliore la vitesse de liaison dynamique.

Si vous souhaitez exposer un membre à Obj-C, vous devez le marquer comme @objc, par exemple:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

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

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

(le programme de migration doit le faire automatiquement pour vous avec des sélecteurs lors de l'exécution avec l'option «minimiser l'inférence» sélectionnée)

Pour exposer un groupe de membres à Obj-C, vous pouvez utiliser un @objc extension:

@objc extension ViewController {

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

Cela exposera tous les membres qui y sont définis à Obj-C, et donnera une erreur sur tous les membres qui ne peuvent pas être exposés à Obj-C (sauf s'ils sont explicitement marqués comme @nonobjc).

Si vous avez une classe où vous avez besoin que tous les membres compatibles Obj-C soient exposés à Obj-C, vous pouvez marquer la classe comme @objcMembers:

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

Maintenant, tous les membres qui peuvent être déduits le @objcseront. Cependant, je ne conseillerais pas de faire cela à moins que vous n'ayez vraiment besoin de tous les membres exposés à Obj-C, étant donné les inconvénients mentionnés ci-dessus d'avoir des membres inutilement exposés.

Hamish
la source
2
Pourquoi Xcode ne le fait-il pas automatiquement lors de la conversion du code avec la dernière syntaxe?
LinusGeffarth
1
Je me demande, sont @IBActionégalement automatiquement @objc? ce n'était pas le cas jusqu'à présent mais ce serait logique. EDIT: NVM, la proposition indique clairement que cela @IBActionsuffit pour qu'une méthode soit @objc.
Sulthan
8
Je ne comprends rien de tout cela. Je n'ai pas de code Objective-C quoi que ce soit. N'existe-t-il pas de méthode Swift pure pour ajouter des cibles à un bouton? Ou utiliser des sélecteurs? Je ne veux pas que mon code soit criblé de cet attribut. Est-ce parce qu'un UIButton est dérivé d'un objet NSObject / Obj-c?
Sti
11
@Sti Donc, pour répondre directement " N'y a-t-il pas de méthode Swift pure pour ajouter des cibles à un bouton? Ou utiliser des sélecteurs? " - non, il n'y a pas de façon "pure Swift" d'utiliser des sélecteurs pour envoyer aux méthodes. Ils s'appuient sur le runtime Obj-C pour rechercher l'implémentation de la méthode pour appeler un sélecteur particulier - le runtime Swift n'a pas cette capacité.
Hamish
12
Les sélecteurs d'hommes sont en désordre. Il semble que toutes les autres mises à jour rapides soient en train de jouer avec. Pourquoi ne pouvons-nous pas simplement avoir un sélecteur de saisie semi-automatique rapide pur pour les méthodes. Rend le code si laid.
John Riselvato
16

Comme documentation officielle Apple . vous devez utiliser @objc pour appeler votre méthode de sélection.

En Objective-C, un sélecteur est un type qui fait référence au nom d'une méthode Objective-C. Dans Swift, les sélecteurs Objective-C sont représentés par la Selectorstructure et peuvent être construits à l'aide de l' #selector expression. Pour créer un sélecteur pour une méthode qui peut être appelée à partir d'Objective-C, transmettez le nom de la méthode, tel que #selector(MyViewController.tappedButton(sender:)). Pour créer un sélecteur pour la méthode de lecture ou de définition Objective-C d'une propriété, transmettez le nom de propriété précédé du libellé getter:ou setter:, tel que #selector(getter: MyViewController.myButton).

Kiran Sarvaiya
la source
tout ce que je comprends de cela, c'est que dans certains cas, je dois ajouter @objc au début d'une fonction, que je l'appelle ou non depuis Objective-C.
J'apprends
10

À partir de, je pense que Swift 4.2, tout ce que vous avez à faire est d'assigner @IBAction à votre méthode et vous pouvez éviter cette annotation @objc idiote

''

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


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}
Logan Sease
la source
1
Cela indiquera également au constructeur d'interface que cette fonction peut être connectée, ce qui n'est peut-être pas ce que vous souhaitez. Il fait également la même chose que @objc.
Josh Paradroid
2

Comme déjà mentionné dans d'autres réponses, il n'y a aucun moyen d'éviter l' @objcannotation pour les sélecteurs.

Mais l'avertissement mentionné dans l'OP peut être réduit au silence en prenant les mesures suivantes:

  1. Accédez aux paramètres de construction
  2. Rechercher le mot-clé @objc
  3. Définissez la valeur de l' interface Swift 3 @objc surOff

ci-dessous est la capture d'écran qui illustre les étapes mentionnées ci-dessus:

Désactivation de l'avertissement "Swift 3 @objc interface"

J'espère que cela t'aides

GUERRIER S1LENT
la source
Comment cela affecte-t-il votre projet dans son ensemble?
Zonily Jame
1
Que diriez-vous de la taille * .ipa ou * .xarchive et du temps de construction?
Zonily Jame
@ZonilyJame je n'ai pas remarqué le temps de construction, mais il n'y avait aucun effet sur la taille de l'IPA
S1LENT WARRIOR
Ce n'est pas une valeur recommandée que vous pouvez utiliser si vous utilisez la version swift 4.0+ selon les directives Apple. Consultez ce help.apple.com/xcode/mac/current/#/deve838b19a1
Bhavin_m
1
Vous pouvez mettre à jour cette réponse en définissant la valeur par défaut dans Xcode 11. Il est important de définir la valeur à la fois sur le projet et sur vos cibles pour supprimer complètement l'avertissement.
Tommie C.
2

Si vous avez besoin de membres objectif c dans votre contrôleur de vue, ajoutez simplement @objcMembers en haut du contrôleur de vue. Et vous pouvez éviter cela en ajoutant IBAction dans votre code.

@IBAction func buttonAction() {

}

Assurez-vous de connecter cette prise dans le storyboard.

Renjish C
la source