Convertir une instance d'une classe en un @protocol en Objective-C

102

J'ai un objet (un UIViewController) qui peut ou non être conforme à un protocole que j'ai défini.

Je sais que je peux déterminer si l'objet est conforme au protocole, puis appeler la méthode en toute sécurité:

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

Cependant, XCode affiche un avertissement:

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

Quelle est la bonne façon d'éviter cet avertissement? Je n'arrive pas à jouer en self.myViewControllertant que MyProtocolclasse.

Gué
la source

Réponses:

171

La bonne façon de procéder est de faire:

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

La UIViewController <MyProtocol> *conversion de type se traduit par «vc est un objet UIViewController conforme à MyProtocol», tandis que l'utilisation se id <MyProtocol>traduit par «vc est un objet d'une classe inconnue conforme à MyProtocol».

De cette façon, le compilateur vous donnera une vérification de type appropriée vc- le compilateur ne vous donnera un avertissement que si une méthode non déclarée sur l'un UIViewControllerou l' autre <MyProtocol>est appelée. idne doit être utilisé dans la situation que si vous ne connaissez pas la classe / le type de l'objet en cours de conversion.

Nick Forge
la source
2
Lorsque vous utilisez des protocoles, vous ne devriez vraiment pas vous soucier du type d'objet - tout l'intérêt d'un protocole est que n'importe quel type d'objet peut l'adopter et être utilisé sans avoir à effectuer un cast vers l'objet spécifique. Donc, je recommanderais d'utiliser la réponse de @andy partout où vous effectuez un casting vers un protocole au lieu de ce qui précède - id<MyProtocol> p = (id<MyProtocol>)self.myViewController;Cette réponse et @andys sont tous les deux corrects, mais le sien est plus correct.
memmons
2
@Answerbot votre commentaire est incorrect, et manque le point que j'ai fait dans le dernier paragraphe de ma réponse. Vous pouvez ou non vous soucier du type d'objet, cela dépend de la situation. Que se passe-t-il si vous souhaitez envoyer un message déclaré UIViewControllerà vcdans l'exemple de ma réponse et qu'il est déclaré comme id <MyProtocol>?
Nick Forge
Vous ne savez pas ce qui est incorrect concernant mon commentaire? Dans tous les cas, si vous vérifiez si un objet est conforme à un protocole, pourquoi appelleriez-vous alors une autre méthode sans rapport avec le protocole? Je ne me souviens pas avoir jamais eu besoin de le faire ou l'avoir vu dans le code que j'ai examiné. Cela me semble une odeur de code.
memmons
Ce n'est pas parce que vous ne l'avez pas vu / utilisé que c'est une odeur de code. Voici un extrait de code montrant un exemple de idproblème dans lequel jeter des informations de type à l'aide de l'utilisation est: gist.github.com/nsforge/7743616
Nick Forge
60

Vous pouvez le lancer comme ceci:

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

Cela m'a un peu gêné aussi. En Objective-C, le protocole n'est pas le type lui-même, vous devez donc spécifier id(ou un autre type, tel que NSObject) avec le protocole que vous voulez.

Andy
la source
Ah, cool, merci. Je viens de vérifier et de voir que le casting (id)fonctionne aussi. Est-ce une mauvaise forme?
Ford
1
Si vous le castez en id <MyProtocol>, le compilateur vous avertira si vous utilisez des méthodes qui ne sont pas définies dans ce protocole.
dreamlax
1
@dreamlax - C'est ainsi que le compilateur vérifie le type par rapport aux protocoles. Voir developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/… pour plus d'informations.
Andy
1
@Ford - il serait préférable d'utiliser spécifiquement le protocole, car de cette façon, le compilateur peut effectuer une vérification de type pour vous.
Andy
1
@Andy, je ne pense pas que vous ayez besoin du '*' puisque 'id' est déjà un pointeur. Donc: id <MyProtocol> p = (id <MyProtocol>) self.myViewController; [p protocolMethod]; Ou simplement: [(id <MyProtocol>) self.myViewController protocolMethod];
Ford