Wie kann ich mit #selector () in Swift 4 mit @objc Inferenzverfall umgehen?

131

Ich versuche, den Quellcode meines Projekts von Swift 3 in Swift 4 zu konvertieren. Eine Warnung, die Xcode mir gibt, betrifft meine Selektoren.

Zum Beispiel füge ich einer Schaltfläche ein Ziel hinzu, indem ich einen regulären Selektor wie folgt verwende:

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

Dies ist die Warnung, die angezeigt wird:

Das Argument von '#selector' bezieht sich auf die Instanzmethode 'myAction ()' in 'ViewController', die von der in Swift 4 veralteten Inferenz des Attributs '@objc' abhängt

Fügen Sie '@objc' hinzu, um diese Instanzmethode für Objective-C verfügbar zu machen

Wenn Sie nun Fixauf die Fehlermeldung klicken, geschieht dies für meine Funktion:

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

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

Ich möchte nicht wirklich alle meine Funktionen umbenennen, um die @objcMarke einzuschließen, und ich gehe davon aus, dass dies nicht erforderlich ist.

Wie schreibe ich den Selektor neu, um mit der Abwertung umzugehen?


Verwandte Frage:

LinusGeffarth
quelle
3
Nein, sich als Markierung @objc ist nun erforderlich, um sie zu Obj-C, zu belichten und damit mit Selektoren zu verwenden.
Hamish
3
Der veraltete Teil schließt also auf öffentlich zugängliche Funktionen als @objc ? Das ist ein bisschen nervig, aber ich mache diese Funktionen im Allgemeinen privat und muss sie @objcsowieso als markieren.
Connor
2
Weitere Informationen zur Änderung finden Sie in SE-0160 . Eine andere Alternative besteht darin, Ihre angegebene Klasse als zu markieren @objcMembers, um alle Obj-C-kompatiblen Mitglieder Obj-C auszusetzen. Ich würde dies jedoch nicht empfehlen, es sei denn, Sie benötigen tatsächlich die Bereitstellung Ihrer gesamten Klasse.
Hamish
3
@LinusGeffarth Wie im Vorschlag erwähnt, würde es wahrscheinlich unnötig die Größe Ihrer binären und dynamischen Verknüpfung erhöhen, was länger dauern würde. Ich denke wirklich nicht, dass es zu viel Aufwand für die zusätzliche Klarheit ist, die Sie speziell für eine bestimmte Sache von Obj-C meinen.
Hamish
1
Versucht, nicht funktioniert.
LinusGeffarth

Antworten:

156

Das Fix-it ist korrekt - es gibt nichts an dem Selektor, das Sie ändern können, um die Methode, auf die es sich bezieht, für Objective-C verfügbar zu machen.

Der ganze Grund für diese Warnung ist in erster Linie das Ergebnis von SE-0160 . Vor Swift 4 internaloder höher wurde davon ausgegangen , dass Objective-C-kompatible Mitglieder NSObjecterbender Klassen @objcObjective-C ausgesetzt waren und daher mit Selektoren aufgerufen werden konnten (da die Obj-C-Laufzeit erforderlich ist, um die Methode nachzuschlagen Implementierung für einen bestimmten Selektor).

In Swift 4 ist dies jedoch nicht mehr der Fall. Es wird nur noch auf sehr spezifische Erklärungen geschlossen@objc , dass sie beispielsweise @objcMethodenüberschreibungen, Implementierungen von @objcProtokollanforderungen und Deklarationen mit impliziten Attributen @objcwie z @IBOutlet.

Die Motivation dahinter, wie im oben verlinkten Vorschlag dargelegt , besteht zunächst darin, zu verhindern, dass Methodenüberladungen beim NSObjectErben von Klassen aufgrund identischer Selektoren miteinander kollidieren. Zweitens hilft es, die Binärgröße zu reduzieren, indem keine Thunks für Mitglieder generiert werden müssen, die nicht Obj-C ausgesetzt sein müssen, und drittens wird die Geschwindigkeit der dynamischen Verknüpfung verbessert.

Wenn Sie ein Mitglied Obj-C aussetzen möchten, müssen Sie es wie folgt markieren @objc:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

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

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

(Der Migrator sollte dies automatisch für Sie mit Selektoren tun, wenn die Option "Inferenz minimieren" ausgeführt wird.)

Um eine Gruppe von Mitgliedern Obj-C auszusetzen, können Sie Folgendes verwenden @objc extension:

@objc extension ViewController {

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

Dadurch werden alle darin definierten Mitglieder Obj-C ausgesetzt und es wird ein Fehler für alle Mitglieder ausgegeben, die Obj-C nicht ausgesetzt werden können (es sei denn, dies ist ausdrücklich als gekennzeichnet @nonobjc ).

Wenn Sie eine Klasse haben, in der alle Obj-C-kompatiblen Mitglieder für Obj-C verfügbar sein müssen, können Sie die Klasse wie folgt markieren @objcMembers:

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

Nun werden alle Mitglieder, auf die man schließen kann @objc, sein. Ich würde jedoch nicht raten, dies zu tun, es sei denn, Sie benötigen wirklich alle Mitglieder, die Obj-C ausgesetzt sind, angesichts der oben genannten Nachteile, wenn Mitglieder unnötig ausgesetzt sind.

Hamish
quelle
2
Warum macht Xcode das nicht automatisch, wenn der Code in die neueste Syntax konvertiert wird?
LinusGeffarth
1
Ich frage mich, sind @IBActionauch automatisch @objc? das war bisher nicht der Fall, aber es wäre logisch. EDIT: nvm, der Vorschlag besagt eindeutig, dass dies @IBActionfür eine Methode ausreicht @objc.
Sulthan
8
Ich verstehe nichts davon. Ich habe überhaupt keinen Objective-C-Code. Gibt es keine reine Swift-Methode zum Hinzufügen von Zielen zu einer Schaltfläche? Oder Selektoren verwenden? Ich möchte nicht, dass mein Code mit diesem Attribut durchsetzt wird. Liegt es daran, dass ein UIButton von einem NSObject / Obj-c-Ding abgeleitet ist?
Sti
11
@Sti Um direkt zu antworten: " Gibt es keine reine Swift-Methode zum Hinzufügen von Zielen zu einer Schaltfläche? Oder verwenden Sie Selektoren? " - Nein, es gibt keine "reine Swift" -Methode zum Verwenden von Selektoren zum Versenden an Methoden. Sie verlassen sich auf die Obj-C-Laufzeit, um die Methodenimplementierung nachzuschlagen und einen bestimmten Selektor aufzurufen. Die Swift-Laufzeit verfügt nicht über diese Funktion.
Hamish
12
Man Selektoren sind ein Chaos. Es scheint, als würde jedes andere schnelle Update damit herumspielen. Warum können wir nicht einfach einen reinen schnellen Autocomplete-Selektor für Methoden haben? Lässt Code so hässlich aussehen.
John Riselvato
16

Als offizielle Dokumentation von Apple . Sie müssen @objc verwenden, um Ihre Auswahlmethode aufzurufen.

In Objective-C ist ein Selektor ein Typ, der sich auf den Namen einer Objective-C-Methode bezieht. In Swift werden Objective-C-Selektoren durch die SelectorStruktur dargestellt und können mithilfe des #selector Ausdrucks erstellt werden. Um einen Selektor für eine Methode zu erstellen, die von Objective-C aufgerufen werden kann, übergeben Sie den Namen der Methode, z #selector(MyViewController.tappedButton(sender:)). Übergeben Sie zum Erstellen eines Selektors für den Objective-C-Getter oder die Setter-Methode einer Eigenschaft den Eigenschaftsnamen, dem das Label getter:oder vorangestellt ist setter:, z #selector(getter: MyViewController.myButton).

Kiran Sarvaiya
quelle
Alles, was ich daraus verstehe, ist, dass ich in einigen Fällen @objc zum Anfang einer Funktion hinzufügen muss, unabhängig davon, ob ich es jemals von Objective-C aus aufrufen werde oder nicht. Ich lerne gerade, dass Swift Obj-C jahrelang benutzt hat
SundialSoft
10

Ab Swift 4.2 müssen Sie Ihrer Methode lediglich @IBAction zuweisen, und Sie können diese dumme @ objc-Annotation vermeiden

`` `

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


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}
Logan Sease
quelle
1
Dadurch wird dem Interface Builder auch mitgeteilt, dass diese Funktion verbunden werden kann, was möglicherweise nicht Ihren Wünschen entspricht. Es macht auch das Gleiche wie @objc.
Josh Paradroid
2

Wie bereits in anderen Antworten erwähnt, gibt es keine Möglichkeit, das zu vermeiden @objc Anmerkung für Selektoren vermieden werden.

Die im OP erwähnte Warnung kann jedoch durch die folgenden Schritte zum Schweigen gebracht werden:

  1. Gehen Sie zu Build Settings
  2. Suchen Sie nach dem Schlüsselwort @objc
  3. Setzen Sie den Wert der Swift 3 @objc-Schnittstelle aufOff

Unten sehen Sie den Screenshot, der die oben genannten Schritte veranschaulicht:

Stummschalten der Warnung "Swift 3 @objc interface"

Hoffe das hilft

S1LENT WARRIOR
quelle
Wie wirkt sich das auf Ihr Projekt im Großen und Ganzen aus?
Zonily Jame
1
Wie wäre es mit * .ipa oder * .xarchive Größe und Build-Zeit?
Zonily Jame
@ZonilyJame Ich habe die Build-Zeit nicht bemerkt, aber es gab keine Auswirkungen auf die IPA-Größe
S1LENT WARRIOR
Dies ist kein empfohlener Wert, den Sie verwenden können, wenn Sie eine schnelle Version 4.0+ gemäß den Apple-Richtlinien verwenden. Überprüfen Sie diese help.apple.com/xcode/mac/current/#/deve838b19a1
Bhavin_m
1
Sie können diese Antwort aktualisieren, indem Sie den Wert in Xcode 11 auf Standard setzen. Es ist wichtig, den Wert sowohl für das Projekt als auch für Ihre Ziele festzulegen, um die Warnung vollständig zu entfernen.
Tommie C.
2

Wenn Sie Ziel-C-Mitglieder in Ihrem Ansichts-Controller benötigen, fügen Sie einfach @objcMembers oben im Ansichts-Controller hinzu. Und Sie können dies vermeiden, indem Sie IBAction in Ihren Code einfügen.

@IBAction func buttonAction() {

}

Stellen Sie sicher, dass diese Steckdose im Storyboard angeschlossen ist.

Renjish C.
quelle