Wandeln Sie eine Instanz einer Klasse in ein @ -Protokoll in Objective-C um

101

Ich habe ein Objekt (einen UIViewController), das möglicherweise einem von mir definierten Protokoll entspricht oder nicht.

Ich weiß, dass ich feststellen kann, ob das Objekt dem Protokoll entspricht, und dann die Methode sicher aufrufen kann:

if([self.myViewController conformsToProtocol:@protocol(MyProtocol)]) {
    [self.myViewController protocolMethod]; // <-- warning here
}

XCode zeigt jedoch eine Warnung an:

warning 'UIViewController' may not respond to '-protocolMethod'

Was ist der richtige Weg, um diese Warnung zu verhindern? Ich kann nicht self.myViewControllerals MyProtocolKlasse wirken.

Ford
quelle

Antworten:

171

Der richtige Weg, dies zu tun, ist:

if ([self.myViewController conformsToProtocol:@protocol(MyProtocol)])
{
        UIViewController <MyProtocol> *vc = (UIViewController <MyProtocol> *) self.myViewController;
        [vc protocolMethod];
}

Die UIViewController <MyProtocol> *Typumwandlung übersetzt in "vc ist ein UIViewController-Objekt, das MyProtocol entspricht", während usingid <MyProtocol> von "vc ist ein Objekt einer unbekannten Klasse, die MyProtocol entspricht".

Auf diese Weise gibt Ihnen der Compiler eine ordnungsgemäße Typprüfung. vcDer Compiler gibt nur dann eine Warnung aus, wenn eine Methode entweder nicht deklariert ist UIViewControlleroder <MyProtocol>aufgerufen wird. idsollte nur in der Situation verwendet werden, wenn Sie die Klasse / den Typ des gegossenen Objekts nicht kennen.

Nick Forge
quelle
2
Wenn Sie Protokolle verwenden, sollten Sie sich wirklich nicht um den Objekttyp kümmern - der springende Punkt eines Protokolls ist, dass jeder Objekttyp ihn übernehmen und verwendet werden kann, ohne auf das bestimmte Objekt umwandeln zu müssen. Also, ich würde mit der Antwort von @andy überall empfehlen Sie ein Protokoll anstelle der oben werfen - id<MyProtocol> p = (id<MyProtocol>)self.myViewController;Diese Antwort und @andys ist beide richtig, aber sein ist mehr richtig.
Memmons
2
@Answerbot Ihr Kommentar ist falsch und verfehlt den Punkt, den ich im letzten Absatz meiner Antwort gemacht habe. Möglicherweise interessiert Sie der Objekttyp oder nicht, dies hängt von der jeweiligen Situation ab. Was passiert, wenn Sie eine Nachricht senden möchten, für UIViewControllerdie vcim Beispiel in meiner Antwort deklariert wurde und die als deklariert ist id <MyProtocol>?
Nick Forge
Nicht sicher, was in Bezug auf meinen Kommentar falsch ist? Wenn Sie auf jeden Fall prüfen, ob ein Objekt einem Protokoll entspricht, warum würden Sie dann eine andere Methode aufrufen, die nicht mit dem Protokoll zusammenhängt? Ich kann mich nicht erinnern, jemals dies tun zu müssen oder dies in Code gesehen zu haben, den ich überprüft habe. Scheint mir wie ein Code-Geruch.
Memmons
Nur weil Sie es nicht gesehen / benutzt haben, heißt das nicht, dass es ein Codegeruch ist. Hier ist ein Code-Snippet, das ein Beispiel dafür zeigt, wo das Wegwerfen von Typinformationen mithilfe von idein Problem ist: gist.github.com/nsforge/7743616
Nick Forge
60

Sie können es so besetzen:

if([self.myViewController conformsToProtocol:@protocol(MyProtocol)])
{
    id<MyProtocol> p = (id<MyProtocol>)self.myViewController;
    [p protocolMethod];
}

Das warf mich auch ein bisschen. In Objective-C ist das Protokoll nicht der Typ selbst, daher müssen Sie zusammen mit dem gewünschten Protokoll id(oder einen anderen Typ, z. B. NSObject) angeben .

Andy
quelle
Ah, cool, danke. Ich habe gerade nachgesehen und gesehen, dass das Casting auch (id)funktioniert. Ist das eine schlechte Form?
Ford
1
Wenn Sie es als ID <MyProtocol> umwandeln, werden Sie vom Compiler gewarnt, wenn Sie Methoden verwenden, die in diesem Protokoll nicht definiert sind.
Dreamlax
1
@dreamlax - Auf diese Weise überprüft der Compiler den Typ anhand von Protokollen. Weitere Informationen finden Sie unter developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/… .
Andy
1
@Ford - Es ist besser, das Protokoll speziell zu verwenden, da der Compiler auf diese Weise eine Typprüfung für Sie durchführen kann.
Andy
1
@Andy, ich glaube nicht, dass du das '*' brauchst, da 'id' bereits ein Zeiger ist. Also: id <MyProtocol> p = (id <MyProtocol>) self.myViewController; [p protocolMethod]; Oder einfach: [(id <MyProtocol>) self.myViewController protocolMethod];
Ford