2016-05-02 4 views
1

Der vollständige Testfall unten soll demonstrieren: Ein Selektor wird, obwohl er an zwei Stellen gleich spezifiziert ist, anders ausgeführt: Entweder wird er an der Klasse oder am Objekt ausgeführt. (Ich verstehe, dass eine statische Methode und eine Objektmethode denselben Namen haben können, aber es gibt nur einen darunter.) Ob der Empfänger Klasse oder Objekt ist, hängt davon ab, wo das "gleiche" selector zu NSNotificationCenter gemacht wird, entweder Klasse Kontext oder in Verfahren Kontext:Ist das Ziel von #selector() abhängig vom Kontext von `addObserver` (Klasse vs Objekt)?

  1. eine statische Methode, den Anruf zu addObserver hat oder
  2. eine Objektmethode hat den Anruf addObserver

während die Anrufe ansonsten identisch sind. Wenn der identische Aufruf in einer statischen Methode auftritt, versucht das System, wenn die Benachrichtigung später verarbeitet wird, den Selektor der Klasse und nicht das Objekt aufzurufen. Die Klasse hat es nicht. Der Code kompiliert gut mit der neuen (in 2.2) Syntax. Ist dieses Ergebnis zu erwarten?

import XCTest 
import class Foundation.NSNotificationCenter // for emphasis 

class SelectorTests: XCTestCase { 

    static let NotificationName = "OneTwoThreeNotification" 

    override func setUp() { 
     super.setUp() 
    } 

    override func tearDown() { 
     NSNotificationCenter.defaultCenter().removeObserver(self) 
     super.tearDown() 
    } 

    func addObserverForTestNormal() { // <- HERE 
     NSNotificationCenter.defaultCenter().addObserver(
      self, 
      selector: #selector(SelectorTests.myMethod(_:)), // <- HERE 
      name: SelectorTests.NotificationName, 
      object: nil) 
    } 

    func testNormal() { 
     self.addObserverForTestNormal() 
     NSNotificationCenter.defaultCenter().postNotificationName(
      SelectorTests.NotificationName, 
      object: self) 
    } 

    static func addObserverForTestStatic() { // <- HERE 
     NSNotificationCenter.defaultCenter().addObserver(
      self, 
      selector: #selector(SelectorTests.myMethod(_:)), // <- HERE 
      name: SelectorTests.NotificationName, 
      object: nil) 

    } 

    func testStatic() { 
     SelectorTests.addObserverForTestStatic() 
     NSNotificationCenter.defaultCenter().postNotificationName(
      SelectorTests.NotificationName, 
      object: self) 
    } 

    func myMethod(x : Int) { 
     XCTAssert(true) 
    } 

} 

Ein Test ist erfolgreich, der andere schlägt fehl. Der Kern des Stack-Trace und die Nachricht ist

„+ [KuckuckTests.SelectorTests MyMethod:]: Unbekannter Selektor gesendet Klasse

Ist diese Spaltung, dh Klasse oder ein Objekt‚abgeleitet‘von addObserver - ? Zusammenhang so offensichtlich zu alt Objective-C Hände, dass sie mit #selector erwähnen in diesem Fall nicht wert ist, könnten Sie eine Dokumentation hinweisen

bearbeiten: habe gerade bemerkt, dass self in der inv statische Funktion ocation von addObserver bezieht sich vielleicht auf die Klasse, nicht auf irgendein Objekt. Das macht den Effekt etwas plausibel, und schlägt vor, dass Programmierer wissen sollten, wofür überladene Namen stehen ...

Antwort

1

Nichts über einen #selector Ausdruck hat irgendeine Verbindung zur Verwendungsstelle dieses Selektors. Ein Selektor benennt eine Nachricht und sagt nichts über den Empfänger dieser Nachricht aus. Sie können einen #selector Ausdruck verwenden, um einen Selector Wert für eine Methode für ein Objekt zu erstellen und diesen Selector Wert an eine API (wie NSNotificationCenter.addObserver oder UIControl.sendAction oder NSTimer.init) weiterzuleiten, was dazu führen wird, dass eine Nachricht mit diesem Selektor an ein völlig anderes Objekt gesendet wird . Diese lose Bindung ist ein intentionaler Teil der dynamischen Natur der Objective-C-Laufzeit, die Cocoa zum Übergeben dieser Nachrichten verwendet (unabhängig davon, ob die Funktionen, auf die sich Ihre Selektoren beziehen, in ObjC oder Swift erstellt werden). Der Ausdruck #selector und die Swift-Funktionsreferenzsyntax, von der er abhängt, geben Ihnen die Möglichkeit, Ihre Verwendung von Selektoren stark zu typisieren, aber nur an einem Ende - damit können Sie überprüfen, ob sich der Selector Wert, auf den Sie bauen, bezieht eine spezifische Methode. (Aber sobald Sie einen Selector Wert haben, ist Swift nicht mehr wie es verwendet wird.

)

Ihre Fehlermeldung (Hervorhebung hinzugefügt):

Unbekannter Selektor an Klasse gesendet

... zeigt an, dass der Fehler ist, weil die Nachricht an die SelectorTests Klasse gesendet wird Objekt (aka das Metaklassenobjekt). Das heißt, wenn Sie eine Benachrichtigung planen, die an self in einer static-Methode gesendet werden soll, fordern Sie einen Anruf an class func myMethod, nicht an func myMethod.

Das Schlüsselwort self bezieht sich immer auf die Instanz, die für den auszuführenden Code verantwortlich ist: Innerhalb einer Instanzmethode bezieht sich self auf die aktuelle Instanz. Innerhalb einer Klassenmethode bezieht sich self auf die (einzige Instanz) des Klassenobjekts.