setNeedsDisplay (Objective C) ne fonctionne pas
Bonjour à tous,
Je me heurte à un problème de mise à jour d'une NSView, un plateau d'échiquier, qui ne se fait pas comme je le souhaiterais.
Ainsi, dans le cas d'une partie opposant un Joueur à l'IA, le temps de réaction du Joueur permet que l'affichage de l'échiquier fonctionne correctement et l'on peut visualiser les coups successifs même si parfois deux coups peuvent s'afficher en même temps...
Le véritable problème survient lorsque je fais jouer l'IA contre elle-même : aucun des coups successifs de l'IA (visibles en console) n'est affiché "en direct" à l'écran et ce n'est qu'après le dernier coup (interruption par échec, mat, ou NSAlert) que la mise à jour de la Vue s'effectue, ce qui est juste frustrant...
J'ai intercalé dans mon code des [maVue setNeedsDisplay:YES] entre les coups joués par l'IA
J'ai forcé l'appel de ces setNeedsDisplay dans le tread principal et non éventuellement en arrière plan, par un :
dispatch_async(dispatch_get_main_queue(), ^{
[maVue setNeedsDisplay:YES];
});
ou par un :
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
[self LancerCoupNoirs];
[viewEC setNeedsDisplay:YES];
});
J'ai retardé l'exécution des coups pour donner à la vue le temps de se rafraichir, par un :
[NSTimer scheduledTimerWithTimeInterval:0.02
target:self
selector:@selector(LancerCoupNoirs)
userInfo:nil
repeats:NO];
ou par un :
[NSTimer performSelector:@selector(LancerCoupNoirs) withObject:self afterDelay:1];
voire un :
NSTimeInterval delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self LancerCoupNoirs];
});
J'ai parcouru le net à la recherche de solutions -ce qui m'a d'ailleurs permis de constater que le problème est bien (trop) connu- mais aucune des solutions que j'ai pu me voir proposer (dont celles ci-avant) n'a fonctionné dans mon cas.
Le Grand Central Dispatch (GCD) revient souvent parmi les propositions mais je ne suis souvent parvenu pour ma part qu'à désynchroniser le déroulement du jeu en exécutant par exemple, successivement plusieurs coups Blancs ou Noirs...
À ce stade donc, je sèche désespérément !
Merci de vos expériences éventuelles à ce sujet, ou d'idées nouvelles à explorer.
Réponses
Effectivement c'est un problème classique. Tout ce qui est interface utilisateur (IHM) doit être exécuté sur le thread principal. Si les calculs sont effectués sur ce même thread, alors ils bloquent l'affichage, qui ne sera rafraîchi qu'à la fin des calculs.
La solution est d'effectuer les calculs sur un thread secondaire. Pour cela, on peut utiliser GCD, mais je te conseille l'utilisation de
NSOperationQueue
et deNSOperation
, qui sont de plus haut niveau, donc plus faciles à utiliser et conviennent tout à fait pour ce que tu veux faire.L'idée est de créer une
NSOperationQueue
série (.maxConcurrentOperations = 1
) et ensuite d'y ajouter le calcul de chaque "coup" sous la forme d'uneNSOperation
(voisNSBlockOperation
, qui est assez pratique).La vraie difficulté sera de communiquer au thread principal que le calcul est terminé. La solution habituelle est d'utiliser une callback: il s'agit d'une closure (= un "block" en ObjC) passée en paramètre, qui sera appelée à l'issue du calcul.
Enfin, attention! L'appel de la callback sera effectué dans le thread courant, donc celui de l'opération. Cela veut dire que ça ne peut pas directement effectuer des opérations d'interface utilisateur (qui doivent s'exécuter sur le thread principal — bis). Il faudra donc appeler les méthodes d'IHM sur le thread principal:
Je ne donne pas beaucoup de code, réclame si besoin.
Bah sinon un tick avec un
NSTimer
qui se répète et qu'on annule dès que la partie est finie.Tu pars avec ça :
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
Avec un block du genre :
Ça t'évite tout un tas de soucis avec un code toujours synchrone qui manipule le modèle et la mise à jour de la vue.
Un tick par seconde et tu peux regarder les parties. Tu peux même t'autoriser une petite coquetterie et mettre un des contrôles de vitesse, un bouton pause, etc...
Tu ferais du Swift je t'aurai conseillé Combine mais bon...
Merci à vous deux, Céroce et Pyroh, vous me redonnez l'espoir qui commençait à manquer
Je me plonge vers ces contrées encore inexplorées et vous tiens au courant des avancées
Même si ça ne coûte pas grand chose d'essayer, je doute que ça fonctionne. Les NSTimers sont exécutés par la Runloop, et si le thread principal est bloqué, alors les itérations de la runloop sont bloquées.
@Cérore et @Pyroh
Des nouvelles pistes vers lesquelles vous m'avez redirigé, j'ai compris qu'il ne s'agissait pas seulement d'exécuter les méthodes d'IHM dans le thread principal, mais tout aussi essentiellement d'exécuter les méthodes de calcul dans un thread secondaire.
Le code auquel je suis parvenu est le suivant :
Dans le principe ça fonctionne plutôt bien, même si 'esthétiquement' il y aurait lieu de ralentir la succession des coups. Mais malheureusement d'autres soucis apparaissent désormais : une interruption brutale du programme après plusieurs coups, accompagnée d'un message d'erreur :
"NSWindow drag regions should only be invalidated on the Main Thread!"
Je suis persuadé que c'est dû à diverses NSAlert présentes dans la méthode gérant les coups de l'IA pour signaler une mise en échec ou un mat, ...NSAlert qui ne supportent pas d'être appelées d'un thread secondaire.
Je suis donc un peu empêtré dans mon code, hésitant à réécrire certaines méthodes pour gérer ces fameuses NSAlert et/ou les threads, ou à créer des versions 'silencieuses' des méthodes impliquées
Je n'abandonne pas pour autant vos propositions de code, mais j'avoue ne pas encore être parvenu à les assimiler complètement, puisque j'ai quelques difficultés pour l'instant à comprendre les NSOperation et à gérer efficacement les blocks...
J'ai donc encore pas mal de choses à régler avant de voir le bout de cette affaire
Tu dois encapsuler tous les appels à l'interface qui auraient lieu dans ton thread secondaire, dans des :
Ça confirme une des solutions envisagées, mais j'ai certaines NSAlert qui utilisent des variables qui elles aussi doivent être encapsulées sans être isolées du reste du code. Ça ne fonctionnera donc pas partout.
D'où ma réflexion plus globale pour une solution standard
Tu as totalement raison sur le principe. Mais comme là le soucis était que les actions s'enchainaient sans qu'on ait le temps de les apprécier je suis parti du principe que le main thread n'était pas trop encombré 😉
Je ne ferais pas ça: cet appel renvoie une queue système partagée, dont tu connais pas vraiment les caractéristiques.
Je réitère que tu devrais créer ta propre queue. Tu peux utiliser GCD, mais NSOperationQueue est plus pratique et repose sur GCD de toute façon.
Ensuite utilise bien une queue série, autrement les coups seront calculés en parallèle, je te laisse imaginer le désastre.
C'était prévisible.
Oui c'est le cas. De toute façon, mélanger IHM et code métier est une mauvaise pratique. Mais les séparer est forcément plus difficile car tu vas devoir trouver un moyen de faire communique ces deux domaines. Typiquement tu peux utiliser la délégation, même si dans le code moderne ou utiliserait peut-être des callbacks sous forme de closures ("blocks").
Je te montre un exemple très simple:
Les closures ne sont pas si difficiles à utiliser mais tu entres dans la programmation concurrente, ce qui n'est jamais simple. L'autre difficulté est la communication des classes.
P.S.: Très utile:
http://fuckingblocksyntax.com
Bonjour à tous,
@Céroce : Merci pour ta proposition de code sur laquelle j'ai travaillé en créant une variable d'instance NSOperationQueue et en l'initialisant comme tel :
puis en l'alimentant de coups successifs :
J'ai également dû créer des copies de pas mal de mes méthodes pour les rendre "silencieuses" (vis-à-vis d'appels à NSAlert) quand je ne parvenais pas à les appeler dans le thread principal.
À ce stade je suis plutôt satisfait du résultat.
Mais il reste néanmoins une méthode récalcitrante, appelée en fin de procédure IA vs IA, qui gâche un peu la fête car je ne parviens pas à l'appeler sur le thread principal sans passer par une refonte trop profonde du code commun avec d'autres fonctionnalités sur lesquelles je ne souhaite pas revenir...
La solution pour que cela fonctionne serait peut-être que je conditionne l'exécution d'une partie du code de cette méthode au thread dans lequel il s'exécute.
D'où la question suivante : existe t-il un moyen d'implémenter un test s'appuyant sur le fait que l'on est dans le thread principal ou non ?
Ça existe:
[NSThread isMainThread]
.Cependant, les seules fois où je l'ai vu était dans des assertions:
Ce qui d'ailleurs n'est plus très utile de nos jours puisque il y a normalement des alertes si on exécute du code AppKit ou Core Graphics sur un thread secondaire.
Je ne cerne pas bien ton problème. Ton moteur de calcul des coups doit de toute façon être séparé de l'affichage. Le calcul va devoir informer l'IHM, c'est pourquoi je te parlais de délégation:
Dans mon exemple, ChessEngine va appeler la méthode sur son thread (secondaire). Dans le délégué, qui s'occupe de l'IHM:
Wopopop ! On mélange thread et queue ici, c'est pas pareil. Il faut oublier
NSThread
si on utilise GCD c'est le plus simple.Plus concrètement @MortyMars c'est toi qui définit l'API donc c'est toi qui sait depuis quelle queue les appels sont réalisés. Généralement tu n'en viens à vérifier sur quel thread/queue le code est exécuté que dans ces conditions :
J'ai pas tout relu mais il me semble que @Céroce t'as donné la marche à suivre. Mais je vais formaliser: tu dois créer un moteur qui va s'occuper exclusivement du calcul des coups. C'est simplement une classe qui contient une représentation du plateau de jeu (un tableau à deux dimension je suppose), une
OperationQueue
série et les méthodes qui modélisent ton algorithme de jeu.Certaines de ces méthodes sont publiques et tiennent lieu d'API. Elle permettent à l'UI d'informer des movements de pièces produites par le joueur (CPU v. CPU c'est autre chose), d'une fonction reset de plateau, éventuellement l'annulation de coup, une fonction pause, etc...
Pour avertir l'UI d'un changement de l'état du plateau il faut utiliser la délégation. C'est généralement le view controller qui tien lieu de délégué. Là je te renvoie au post de @Céroce juste au dessus.
Maintenant on résume :
HUMAN vs. CPU
1. L'UI informe le moteur d'un mouvement du joueur
2. Le moteur calcule sur la queue la validité du coup
3. Le moteur informe de la validité du coup via le délégué
4. Si invalide on informe le joueur et on met à jour l'UI en prenant bien soin d'exécuter le tout sur la main queue via
dispatch_async(dispatch_get_main_queue(), ^{ ... }
puis on reviens en 1. Si valide on bloque la possibilité pour le joueur de déplacer des pièces (sur la main queue aussi).5. Le coup pour le CPU est calculé sur la queue du moteur.
6. Une fois le calcul terminé on informe l'UI du coup du CPU via le délégué et on dispatche sur la main queue la fonction de mise à jour du plateau.
7. Le moteur calcule la condition de victoire et informe le délégué du résultat. Si le résultat est nul, on débloque la possibilité de manipuler les pièces et on revient en 1. Sinon on gère l'issue de la partie depuis la main queue et on met le jeu dans un mode qui impose un reset.
CPU vs. CPU
1. L'UI informe le moteur qu'il faut commencer à jouer.
2. Le moteur calcule sur la queue le coup du CPU1.
3. Le moteur informe le délégué du coup joué. L'UI est mise à jour sur la main queue.
4. Éventuellement le moteur fait une pause de quelques seconde histoire de voir les coups s'enchaîner.
5. Le moteur calcule sur la queue le coup du CPU2.
6. Le moteur informe le délégué du coup joué. L'UI est mise à jour sur la main queue.
7. Le moteur calcule la condition de victoire et informe le délégué du résultat. Si le résultat est nul, éventuellement le moteur fait une pause de quelques seconde histoire de voir les coups s'enchaîner et on revient en 2. Sinon on gère l'issue de la partie depuis la main queue et on met le jeu dans un mode qui impose un reset.
Pour séparer correctement UI et moteur je te conseille de conserver une modélisation du plateau coté moteur et côté UI. Au départ elles sont identiques et correspondent au placement de départ des pièces. Ensuite le moteur manipulera sa propre modélisation et informera des changement via le délégué. Là la modélisation coté UI est mise à jour et l'affichage est mis à jour sur base de ces données. Mais j'avais l'UI ne doit manipuler elle-même sa modélisation du plateau !
Ça peut paraître contre-intuitif de dupliquer de la données mais ça va te forcer à vraiment considérer moteur et UI comme deux parties distinctes qui ne font que s'échanger des commandes et se communiquer des données. C'est un très bon exercice.
Bonjour à tous, et merci à Céroce et Pyroh pour leurs retours toujours détaillés et didactiques
@Céroce :
Merci !
C'est pile poil ce que j'espérais mais que j'ai été incapable d'exhumer du net. Mon Google ne s'est pas montré coopératif sur ce coup...
Je vais tester ça
C'est le cas, ce sont deux classes distinctes : 'Minimax' pour le moteur et 'ChessView' pour l'affichage et la MàJ du plateau.
Grâce à ton aide 'ChessView' est convenablement gérée par NSOperationQueue et NSOperation
Mais par choix perso, quelques boites de dialogues (NSAlert) "agrémentent" le moteur (classe 'Minimax') pour signaler par exemple une mise en échec, un mat, ou un pat ; et c'est l'affichage de ces boites de dialogues qui ne sont pas forcément exécutées dans le thread ppal qui plante le programme.
J'en ai désactivé une grande partie en créant des copies de méthodes pour les rendre "silencieuses", mais il reste une méthode récalcitrante que je ne parviens pas à aménager sans remettre en cause les fonctionnalités de bases (Joueur vs IA notamment)
J'ai semble t-il zappé cette piste concernant la délégation, que je ne maitrise d'ailleurs pas (non plus)
Je mets ça en haut de la pile car je sens que tu vas me suggérer de reporter les NSAlert évoquées plus haut de 'Minimax' vers 'ChessView' par un processus de délégation, et j'en ai d'avance des sueurs froides
@pyroh
Ça n'est pas mon cas, je le jure ...
... et on est bien en 2022 où le GCD me file des boutons
Ne dis pas que ça vient de moi, mais il est vraiment trop fort (lui aussi )
Cette fonctionnalité de base de l'appli fonctionne plutôt conformément à mes attentes (à la performance du moteur près), d'où ma réticence à reprendre le code jusqu'à la remettre en cause.
Hors délégation, le fonctionnement est conforme à cette séquence
La condition de victoire est vérifiée avant et après chaque coup, ce qui génère des messages d'alerte (c'est un choix perso) qui, finalement, posent problème lorsqu'ils sont appelés d'un thread secondaire...
C'est le cas je pense, car ce sont deux classes séparées mais communiquant entre elles : ChessBoard pour les données et ChessView pour l'UI
Je note le conseil
Dans un premier temps, compte tenu de la facilité de mise en oeuvre, je vais tenter d'exploiter [NSThread isMainThread] et voir si je peux m'en sortir ainsi, même si je dois faire quelques concessions vis-à-vis de l'élégance du code
Sinon je crains de devoir mettre les mains dans le cambouis de la délégation...
Je donnerai des nouvelles.
Merci encore à vous deux
Bonjour à tous,
@Céroce et Pyroh :
Concernant ma 'méthode récalcitrante', résolue provisoirement par le biais d'un test sur [NSThread isMainThread], je dois avouer qu'elle masquait un problème bcp plus élémentaire (shame, shame, shame, shame on me) : dans le module IA vs IA, ma boucle de succession des coups Blancs puis Noirs (ou inversement) ne s'interrompait jamais dès un Mat ou un Pat car mon 'While' testait dans le thread principal des variables modifiées dans un thread secondaire, ce qui ne pouvait évidemment pas marcher...
Il a suffit d'intégrer la boucle while dans le même thread que les coups successifs pour que ce dysfonctionnement disparaisse.
[NSThread isMainThread] aura eu très indirectement le mérite de cette mise en évidence.
Le module IA vs IA est désormais fonctionnel, si ce n'est qu'il est pour l'instant privé -a minima- d'une boite de dialogue NSAlert (du moteur) indiquant sur quoi se termine la partie.
Pour Ie reste, il me faut donc désormais me plonger dans les méandres de la délégation pour voir comment résoudre cette petite contrariété, dans la mesure où il s'avérera que ça en vaut la peine
La délégation, tout le monde s'en fait des montagnes; j'ai donné des formations au développement iOS plusieurs années, je parle d'expérience. Mais franchement ce n'est pas compliqué, le plus difficile est de comprendre qui fait quoi. D'ailleurs souvent, on l'a déjà utilisé sans se poser de questions.
Pour donner un exemple, ton application comprend certainement une classe AppDelegate, qui se conforme au protocole
NSApplicationDelegate
:NSApplication possède également une propriété:
Ça veut dire que l'application va avertir son délégué (delegate) quand certains événements se produisent. Les messages qui avertissent sont contractualisés par le protocole
NSApplicationDelegate
. Par exemple:est appelé par l'application pour dire à son délégué qu'elle a fini de se lancer.
Pour récapituler, c'est bien l'application qui délègue une partie de son fonctionnement à un délégué (ici l'AppDelegate).
Pour ton moteur c'est pareil.
ChessBoard
va déléguer une partie de son fonctionnement à son délégué:Au niveau de l'implémentation, dans
ChessBoard
:Dans ChessView:
Et pour finir, il faut dire QUI est le délégué de
ChessBoard
(par exemple dans le WindowController):Si on ne le fait pas, il ne se passe rien: le message sera envoyé à self.delegate qui est nil, et en ObjC, envoyer un message à nil n'a pas d'effet (pas même un plantage).
Cela devait être plus facile dans le temps avec
UIAlertView
&UIAlertViewDelegate
, un exemple simple et compréhensible je trouve.Je vais rajouter que le
delegate
, c'est vraiment de la délégation. C'est un moyen de faire communiquer 2 objets sur un « plan similaire » (Objet A possède ObjetB, ou ObjectC possède ObjetA et ObjetB souvent).Cela permet à un objet de signifier tous les moments importants de sa vie (ah tien, on a cliqué à tel endroit, on est arrivé à telle montant, etc. ). Tu avertis ton
delegate
, et s'il considère qu'il faut réagir ou non. Un peu comme si à chaque étape de ton travail, tu devais dire où tu en es à ton supérieur. Il te dira peut-être "c'est bien", "continue", ou "stop", fais autre chose, etc en fonction des étapes.En Swift, la délégation est de plus en plus oubliée au profit des blocks/closures (qu'on pourrait également faire en Objective-C d'ailleurs, j'ai eu un collègue fan). Cela a ses avantages et ses inconvénients.
Merci à vous deux, Céroce et Larme pour les exemples de codes et les précisions apportées.
Pour mon cas précisément, puisqu'il s'agit de NSAlert de ma classe Minimax, qui posent problème quand elles ne sont pas appelées dans le thread ppal, je pense que je vais transposer le code de Cérore en faisant de Minimax le délégant, et effectivement ChessView le délégué.
Le processus me parait, à ce stade précédant l'implémentation plutôt clair.
Cependant il reste une interrogation concernant la dernière précision de Céroce
Quand tu dis de placer cette déclaration dans le WindowController, s'agit-il bien de ma classe Contrôleur ?
Et si oui pourquoi dans cette classe et pas dans ChessBoard (de ton exemple) ou ChessView, ce qui me paraitrait plus approprié ?
Une vue ne doit pas être un délégué sauf dans quelques cas particuliers.
Dans l'idéal c'est le contrôleur qui est le délégue. Ce qui fait que quand un truc se passe de la vue vers le moteur :
Input utilisateur dans la vue -> Contrôleur -> Mise à jour du moteur
et du moteur vers la vue :
Event moteur -> Contrôleur -> Mise à jour de la vue
Les classes de vue (ici ChessBoard) et de modèle (le moteur) ne doivent pas se connaitre. C'est le contrôleur qui connait les deux et qui les lie. De cette manière tu peux changer ta vue ou ton moteur sans que l'autre ne soit impacté.
C'est toute la logique de l'analogie avec un travailleur et son supérieur.
Est-ce toi qui va pendre les décisions/réagir ? Alors, tu es ton propre
delegate
.Est-ce ton supérieur qui va prendre les décisions/réagir ? Alors, c'est ton
delegate
.Ici, en architecture MVC, c'est souvent le "C" de Controller, qui contrôlera.
Je ne peux pas te répondre, ne sachant pas ce qu'est ta classe Contrôleur.
Historiquement, sur Mac on utilise des NSWindowController pour gérer une fenêtre, mais depuis quelques années, les NSViewControllers existent également sur macOS. Donc, tu vois quelle est la bonne classe dans ton code.
Parce qu'il faut que l'objet qui fait l'affectation ait accès à la fois à la ChessBoard et à la ChessView.
Je suis d'accord, et j'aurais dû t'orienter vers cette architecture:
Merci à vous trois, Pyroh, Larme et Céroce
Au stade de l'implémentation je constate que (pour moi) la délégation n'est pas aussi simple ...
1) J'ai eu plusieurs réactions inattendues comme des Protocoles non reconnus par le compilateur ; et l'ajout inconsidéré d'importation de fichiers d'entête pour tenter de résoudre le souci, a mis le bazar un temps dans le projet...
Je m'en suis sorti par une pirouette en repositionnant quelques #import et en déclarant quelques @class...
2) La délégation se faisant -au niveau du code- entre instances et non entre classes proprement dites, elle ne pourra pas s'appliquer au moteur, qui est une classe sans instances créées et définissant uniquement des Méthodes de Classe.
Mais je peux effectivement la mettre en place entre ma Vue et mon Contrôleur : c'est ce sur quoi je travaille en ce moment.
3) Enfin, quand il s'agit de définir qui est le délégué de qui, la déclaration...
...devra être faite après instanciation des deux objets concernés, ce qui complique un peu plus le fait de trouver le bon endroit pour le faire.
Grâce à vous j'ai tous les outils en mains pour avancer : merci encore
Comme d'hab je donnerai des nouvelles, qui seront j'espère celles du succès de l'opération
Bonjour à tous,
Nos vies sont balisées par les grands évènements auxquels on se confronte : un premier cri, un premier pas, un premier pote, une première pote, et vice-versa, ... et pour les plus chanceux une délégation de classe en Objective C
Je fais partie des chanceux car, oui, je viens de mettre en place mon premier protocole de délégation Obj-C
Ma classe délégante :
Ma classe déléguée :
ainsi que :
La déclaration de lien de délégation :
Et enfin, l'appel de la méthode via la classe délégante :
Ça fonctionne...
Même si ici ça n'a qu'une utilité purement pédagogique, car plutôt que d'appeler la méthode (déclarée) de la classe délégante, j'ai plus intérêt à appeler directement sa jumelle (définie) de la classe déléguée.
Je me dis que tout ça c'est pour le fun, et que peut-être je saisirai un peu plus tard toute la subtilité de ce protocole et les possibilités qu'il offre réellement
@MortyMars félicitations!
Tu verras que ça va devenir naturel, quand tu auras bien établi quel objet délègue et lequel est le délégué.
Au départ, on peut se demander pourquoi déclarer un protocole, mais ça sert à formaliser les choses. Aussi l'objet qui délègue ne sait rien de son délégué (défini avec le type générique
id
) si ce n'est qu'il se conforme au protocole. C'est l'application d'un des principes SOLID (inversion de contrôle). Ça permet de découpler la classe qui délègue de la classe déléguée.J'ai malgré tout deux messages d'erreur, non bloquant, lors de la compilation, dont je n'arrive pas à me débarrasser :
Encore une fois, ça compile et fonctionne et je peux donc vivre avec, mais il y a tout de même qqchose qui n'est pas optimal...
Je n'ai rien trouvé d'interessant ou de correspondant sur le net.
Quelqu'un aurait déjà eu ça ?
Dans MCNConnecteur.h:
Tu as dû commettre une erreur, parce qu'il n'y a aucune raison que
ChessView
se conforme àChessViewDelegate
. C'est son délégué (MCNConnecteur
) qui doit s'y conformer.Bonjour Céroce,
Oui, tu as raison dans l'entête de ChessView, j'avais :
Ce qui suppose que ChessView se conforme au protocole de délégation de ... ChessView
J'ai corrigé et le message d'erreur correspondant a disparu
Pour le second problème, dans l'entête MCNconnecteur.h j'ai ajouté : #import "ChessView.h"
Et dans l'entête ChessView.h j'ai supprimé : #import "MCNconnecteur.h" pour éviter les importations en boucle, et l'ai 'remplacé' par @class MCNconnecteur
et
Ça compile toujours mais j'ai également toujours dans l'entête MCNconnecteur.h le message de Xcode signalant au niveau de
un " Cannot find protocol definition for 'ChessViewDelegate' "
Dans MCNConnecteur.h, retire
En effet cette déclaration signale au compilateur: "sache qu'il existe un protocole qui s'appelle ChessViewDelegate". Or ça ne sert à rien puisque celui-ci est déclaré dans
ChessView.h
que MCNConnecteur.h importe.Deuxièmement, dans ChessView.h, retire:
Parce que ChessView n'a pas besoin de savoir qu'il existe une classe
MCNConnecteur
. Elle doit juste savoir que son délégué se conforme àChessViewDelegate
.Je ne suis pas sûr que ça règlera tous les problèmes, aussi il est important que tu comprennes la logique pour les régler toi-même.
Dans ChessView.h, je peux effectivement supprimer @class MCNconnecteur;
Mais si je supprime dans MCNConnecteur.h, @protocol ChessViewDelegate;
alors j'ai carrément un "Build Failed"...
Alors vérifie ce que tu as tapé. Si le protocole est défini dans
ChessView.h
et qu'il y a bien un#import ChessView.h
dansMCNConnecteur.h
, alors ça doit fonctionner.