(Réglé) Erreur rechargement d'une vue avec UITableView
Bonjour à tous, je me retrouve confronté à un problème avec une UIView contenant une UITableView d'infos.
Dès que j'arrive sur la vue une première fois pas de problème notable.
Cependant si je recharge ma vue ou que je vais sur la page de détail des infos, et que je cherche à revenir à la vue précédente via:
MyViewController* myViewController = [[MyViewController alloc]init];
[self.navigationController pushViewController:myViewController animated:NO];
Je me retrouve face à cette erreur.
2014-04-23 09:33:50.544 MonApp[801:60b] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds for empty array'
*** First throw call stack:
(
0 CoreFoundation 0x0000000101d10495 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x0000000101a6f99e objc_exception_throw + 43
2 CoreFoundation 0x0000000101cb6745 -[__NSArrayM objectAtIndex:] + 213
3 MonApp 0x000000010000de42 -[MyViewController tableView:cellForRowAtIndexPath:] + 1362
4 UIKit 0x00000001006f0f8a -[UITableView _createPreparedCellForGlobalRow:withIndexPath:] + 348
5 UIKit 0x00000001006d6d5b -[UITableView _updateVisibleCellsNow:] + 2337
6 UIKit 0x00000001006e8721 -[UITableView layoutSubviews] + 207
7 UIKit 0x000000010067c993 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 354
8 QuartzCore 0x0000000104a32802 -[CALayer layoutSublayers] + 151
9 QuartzCore 0x0000000104a27369 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 363
10 QuartzCore 0x0000000104a271ea _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
11 QuartzCore 0x000000010499afb8 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 252
12 QuartzCore 0x000000010499c030 _ZN2CA11Transaction6commitEv + 394
13 UIKit 0x000000010061b024 _UIApplicationHandleEventQueue + 10914
14 CoreFoundation 0x0000000101c9fd21 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
15 CoreFoundation 0x0000000101c9f5f2 __CFRunLoopDoSources0 + 242
16 CoreFoundation 0x0000000101cbb46f __CFRunLoopRun + 767
17 CoreFoundation 0x0000000101cbad83 CFRunLoopRunSpecific + 467
18 GraphicsServices 0x0000000103d41f04 GSEventRunModal + 161
19 UIKit 0x000000010061ce33 UIApplicationMain + 1010
20 MonApp 0x000000010001c853 main + 115
21 libdyld.dylib 0x00000001023a85fd start + 1
22 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
Merci d'avance à ceux qui m'aideront.
PS: Ceux qui commenteront uniquement pour souligner mon faible niveau sont priés de s'abstenir.
Réponses
Sans le code il va me la falloir...
Salut AliGator, et merci de ta réponse
Il faut que tu apprennes à comprendre tes messages d'erreurs, et réduise le champ de cause de ton erreur.
Là , il dit que tu as essayé d'accéder à un index de ton array qui n'existe pas, car il est vide.
Utilise des BreakPoint également, ou des NSLogs si tu veux pour savoir dans quelle méthode tu passes, à quel endroit, etc.
Salut Larme,
J'ai bien compris que l'index 1 est en dehors des bornes de mon tableau vide, mais je ne vois pas vraiment pourquoi la 1ère fois mon tableau se remplis, et pourquoi ce n'est pas le cas au 2nd passage
Met des breakpoint et fais du pas à pas pour déjà comprendre à quelle ligne exactement ça plante dans la méthode, et pour en profiter pour inspecter un peu quel est le contenu de tes variables.
Tu comprendras alors qu'elle variable est problématique, quel tableau est devenu plus petit que ce que tu ne crois. Et du coup essayer de comprendre pourquoi il a changé dans le cas ou tu fais back par rapport au premier affichage.
Bon j'ai trouvé la source du problème qui est donc dans la méthode getAll donnée plus haut (puisque quand je commente cette ligne plus de souci):
Ma méthode getOptionFromId est la suivante:
J'ai beau regarder mes 2 tableaux (que sont tempTab et tabOpt) sont tous les 2 déclarés et initialisés (via le alloc init), donc je vois vraiment pas d'où ça peut venir
Il y a bien un moment où ces tableaux (ou leurs sous-tableaux) contiennent moins d'éléments que ce que la tableView croit, d'où le OutOfBounds que tu as.
Et comment puis je savoir cela s'il te plait ?
J'ai beau mettre des Logs ça me les affiche au 1er passage, mais pas au 2nd
Met un breakpoint dans cellForRowAtIndexPath et utilise le mode pas à pas pour avancer ligne de code par ligne de code.
(Au premier passage vu que tu sais que ça ne va pas crasher tu peux faire "Continue" pour continuer d'exécuter le code, mais au 2ème passage tu fais du pas à pas pour comprendre sur quelle ligne ça crash)
Une fois que tu as trouvé à quelle ligne de ton code de cellForRowAtIndexPath: l'appli se met à crasher, tu vas pouvoir relancer l'appli et retester à nouveau mais en t'arrêtant pile juste avant la ligne, et regarder un peu plus en détail ce qu'il y a dans tes variables. Quitte à décomposer cette ligne en plusieurs lignes intermédiaires pour comprendre ce qui se passe étape par étape, genre ne pas faire un [[tab objectAtIndex:i]objectAtIndex:j] directement, mais faire un subtab = [tab objectAtIndex:i] sur une ligne et un [subtab objectAtIndex:j] sur la ligne suivante, pour décomposer et comprendre ce qui se passe.
Enfin bref, tu fais du déboggage, quoi. T'as déjà bien dû en faire dans d'autres langages, Java ou autre d'après ton parcours, y'a rien de nouveau, tu décomposes ton problème pas à pas pour isoler et comprendre exactement où ça pose problème, quelle ligne et quelle instruction crash, et en regardant un peu les variables tu essayes de comprendre pourquoi. Du déboguage classique quoi.
Merci AliGator pour cette réponse.
Je t'avoue que durant mon parcours (que ça soit scolaire ou pro) j'ai jamais eu de débogage à faire, donc ça va vraiment être une découverte pour moi, j'ai un peu touché hier aux breakpoint, et donc au débug, et ça me paraissait relativement compliqué.
Je vais essayer ça, et si vraiment j'ai trop de souci, je reviendrais vers toi ou vers un autre capable de m'aider
Bon donc au 2nd passage,
Il passe une première fois dans la fonction sans que ça plante.
Ensuite il m'envoie sur 2 pages de thread - Thread 1: "0 objc_autoreleaseReturnValue" et "0 -[UITableView _updateVisibleCellsNow:]" .C'est normal selon vous ?
Et visiblement la dernière ligne qu'il me lis avant de planter est la suivante:
Voila ce qu'il m'affiche concernant cette ligne:
Après je sais pas ce que tu fais avec cette liste, tu dois sans doute la vider à un moment et oublier de la re-remplir.
Bah visiblement non je ne la vide à aucun moment c'est ça qui est plutot rare
Les différentes occurences de liste ci dessous:
methode executePostCall
methode getAll
methode cellForRowAtIndexPath
methode numberOfRowsInSection
methode didSelectRowAtIndexPath
Et 4 instances de liste passées en commentaire
Heu et reloadrecupall2 il est pas vide par hasard ?
En quoi c'est une hérésie de le mettre en static ?
Concernant le reloadrecupall2
.h
Bon je n'y comprends plus rien, ça marche correctement actuellement, alors que ça marchais pas ce matin, et que j'ai rien modifié
Au final, en plus des problèmes que ça peut te poser si tu ne l'utilises pas correctement, cela viole le principe de base de la POO où chaque objet a des propriétés/variables d'instance.
Faut utiliser des @property (ou des variables d'instance à la limite bien que maintenant c'est un peu obsolète). Ca fait partie des premières choses qu'on apprend dans les bouquins sur ObjC...
(Je ne sais pas comment tu as appris l'ObjC, via des tutos, via un bouquin, via une formation...?)
Tu fais bien de me le dire, car j'ai nombre de propriétés en static.
Et j'ai appris l'Objective C, par le biais de ma formation scolaire par des cours donné par un pro (enfin c'est ce qu'il nous dit ^^) dans le domaine de l'iOS (Son entreprise fait de la délégation de formateurs, des applications sur mesure, et des formations en informatique).
Je dis pas, ça peut parfois servir d'utiliser des variables statiques globale (en particulier pour le pattern singleton, je crois que c'est le seul cas où j'en utilise, et encore c'est des variables statiques donc le scope est réduit à la méthode sharedIntsance donc pas globales), mais c'est très très rare, et quand tu le fais faut avoir conscience des conséquences. Des constantes déclarées static, pourquoi pas ("static NSString* const kSomeString = @toto; par exemple), mais des variables statiques, non seulement c'est pas du tout dans la logique POO mais en plus tu vas au devant de bugs (type multithreading/race conditions/...) que tu risques d'avoir du mal à corriger.
Il y a peut être des concepts de POO à réviser, là , comme le concept d'objets et de variables d'instance, etc. parce que c'est quand même un concept de base en Objective-C (et en POO en général)
C'est vrai qu'à part dans les singletons/sharedinstance et des NSString (notamment pour les cellIndentifier des UITableViewCells), j'en utilise pas non plus.
D'accord, merci je prends note de tes précieux conseils
Un int ou un BOOL en static ça passe ou vaut mieux aussi le mettre en property ?
- Pour les singleton, ce sont des variables (et non des constantes) statiques, en effet, mais d'une part restreintes à un scope local et pas global, et en plus c'est le seul cas où ça a du sens de les utiliser
- Par contre pour les NSString comme les cellidentifier, ce ne sont pas des variables statiques, mais des constantes. Autant les variables globales c'est le mal (car leur accès n'est pas protégé ni thread-safe, et tout le tintouin), autant les constantes c'est le bien, et si ces constantes doivent avoir leur scope limité au fichier (et pas accessibles de l'extérieur), le mot clé "static" a alors tout son sens, mais je le répète dans ce cas ce ne sont pas des variables statiques mais des constantes, donc ça n'est pas soumis aux problèmes/risques évoqués plus haut
Non, ça ne fait aucunement exception. "static int x" ou "static BOOL y" sont tout aussi mal et dangereux (en terme d'accès concurrentiel et risques sur le multi-threading, entre autres choses) que "static NSString* x" ou que "static NSArray* y", ça ne change rien."static int const kWidth = 320;" là par contre ça va, c'est une déclaration de constante, donc qui ne sera accédée qu'en lecture seule, donc pas de risque d'une lecture et une écriture concurrents générant un risque de race condition et de crash.
Mais au delà de leur utilisation pour déclarer une constante, ou d'une utilisation locale maà®trisée dans un cas très très particulier (le cas du singleton étant le seul que je vois qui soit acceptable), c'est les seuls cas où ça a du sens.
Dans le cas où ton int ou ton BOOL a sémantiquement le sens d'une variable d'instance / propriété (en sens du vocabulaire de la POO, c'est pas restreint à Objective-C, ça, ce sont les bases même de tout langage Objet), alors utilise une @property.
Conceptuellement de toute façon ça n'a pas de sens de déclarer une variable globale alors que ce que tu manipules est attaché à l'instance de ton objet, et non pas global. C'est comme si tu gérais un parc de voitures et qu'au lieu de déclarer une classe "Voiture" qui a une propriété "couleur" et "marque" et "modèle", tu mettais la "couleur" dans une variable statique et non une propriété... variable statique qui du coup est globale et pas liée à ton instance. Du coup toutes les Voitures manipulées par ton application partageraient la même variable "couleur", au lieu que chaque voiture ait sa propre couleur...
J'ai l'impression qu'il te faut réviser tes bases de POO...
Je veut bien qu'on me dise quel(s) attribut(s) mettre pour des int ou des Bool, car que je mette retain, strong, nonatomic, assign, cela me fait une erreur ^^
Pour les Array, String, MutableArray, les attributs nonatomic et retain passent mais pas pour le reste ^^
Sinon aurait tu oublié de me répondre pour ma UIScrollView Ali ? ^^
(C'est pas dit méchamment, je sais que tu fais autre chose à côté)
On ne met pas "retain" comme ça par hasard. D'ailleurs depuis ARC (qu'on utilise par défaut pour les nouveaux projets), le mot "retain" est obsolète, maintenant en terme de politique d'ownership, on parle de "weak" ou "strong" pour les objets, et "assign" pour les types scalaires.
Ca ne servirait pas à grand chose que de te dire "bah met assign pour un BOOL" sans t'expliquer la signification exacte et les conséquences de ces mots clés.
Quand à "nonatomic" c'est un autre mot clé qui n'a plus à voir avec la politique d'ownership de la propriété, mais avec la façon dont les accesseurs doivent être codés, en particulier s'ils doivent être atomiques ou pas pour prévoir un lock en cas d'utilisation multithread ou pas. Et "readonly" ou "readwrite" de même sont encore d'autres attributs d'une propriété qui définient entre un 3e axe n'ayant rien à voir avec strong/weak/assign/unsafe_unretained)
Tout ça est un vaste sujet, que là encore un bon bouquin va mieux t'apprendre qu'une réponse de 3km sur un forum où il y a trop à expliquer.
Bon je vais devoir revoir mon code, depuis que j'ai passé de static à @property il y a certains trucs qu'il n'apprécie pas ^^
AliGator tu ne sais pas m'aider sur la UIScrollView stp ? ^^
Si tu en es à cette étape, y a plusieurs anomalies à corriger :
Les conventions d'appellation Cocoa : getAll, strResult,....
Utilise les constantes la ou il faut : http://mywebsite/getOptions.php,...
....
Lien documentation sur les conventions d'appellation Cocoa :
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/codingguidelines/Articles/NamingMethods.html
Merci mais je ne vois pas où est le souci.
Ce n'est pas vraiment des erreurs mais des bonnes pratiques à prendre en compte :
Pour que les autres développeurs comprennent ton code facilement ( si tu travailles en équipe par exemple) il faut respecter certaines convention d'appellation (méthodes, classes, variables,...) d'Apple. Prenons exemple de ton code.
Exemple 1 :
la méthode "getAll".
Deux anomalies dans ce nom de méthode :
1. Clarté : Y a aucun développeur qui va comprendre ce qu'elle fait ta méthode en lisant juste sa signature "getAll", on comprend qu'elle récupère tout mais quoi (des voitures, des articles,....). Il faut que les autres développeurs qui lisent ton code ( pour maintenance par exemple,...) puissent comprendre ce qu'elle fait ta méthode sans rentrer dans les détails. Donc la clarté des appellation documente ton code.
Exemple 2 :
"reloadrecupall2" : Pourquoi 112 ? à mon avis il n'a rien à faire ici.
2. Dans les conventions d'appellation d'Apple, l'utilisation de "get" dans les noms de méthodes n'est pas nécessaire sauf si une ou plusieurs valeurs sont retournées par références.
Y a encore d'autres anomalies par rapport aux conventions d'appellation dans ton code :
- Consistance :
Dès fois tu utilise "strResult" et "stringResult". Ce n'est pas consistant.
Tu mélanges le Français et l'anglais : "imgLink", "lienImg"
- Abréviation : tu utilises "str" par exemple, utilise plutôt "string", prend exemple des API Apple ( stringValue,...).
...
- Utilisation des constantes :
Par exemple dans ton code : http://mywebsite/getOptions.php
Tu vas surement avoir d'autres méthodes qui vont faire appel à tes Web Service,et aussi il viendra un jour ou tu vas avoir besoin de changer ton api ( replacer l'api de développement par celle du production,....)donc au lieu de chercher dans toutes les lignes du code "http..." et les changer, tu n'as qu'à modifier ta constante dans un seul endroit.
Et y a encore d'autres choses dans ton code que je te laisse découvrir tout seul .
Merci d'avoir pris le temps de m'expliqué