Problème [__NSDictionnaryI length]

Bonjour à  tous,


 


J'ai actuellement un problème qui me fait planter mon application, l'erreur étant la suivante:


 



 


Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI length]: unrecognized selector sent to instance 0x7af82a60'



 


En cherchant un peu sur stackoverflow, je suis tombé sur différents sujets:


 


http://stackoverflow.com/questions/23359356/nsdictionaryi-length-unrecognized-selector-sent-to-instance-trying-to


 


http://stackoverflow.com/questions/22966650/nsdictionary-length-unrecognized-selector-sent-to-instance


 


http://stackoverflow.com/questions/25296242/nsdictionaryi-length-unrecognized-selector-sent-to-instance-error-withou


 


http://stackoverflow.com/questions/17057891/nsdictionaryi-setobjectforkey-unrecognized-selector-sent-to-instance-0x9


 


J'ai cru comprendre que le problème venait du fait qu'une longueur de NSString était demandée, d'où l'erreur. Seulement j'ai pas l'impression de lui demander la longueur d'un String.


 


Voici mon code:



-(void)getInfosContact
{
    NSString *post =[[NSString alloc] initWithFormat:@idCo=%d&;local=%@",`007,@Grenoble];
    
    NSLog(@Données reçues : %@",post);
    
    NSURL *url=[NSURL URLWithString:@http://extranet.monsite.com/myApp/V2/contact.php];
    
    NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    
    NSString *postLength = [NSString stringWithFormat:@%lu, (unsigned long)[postData length]];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:url];
    [request setHTTPMethod:@POST];
    [request setValue:postLength forHTTPHeaderField:@Content-Length];
    [request setValue:@application/json forHTTPHeaderField:@Accept];
    [request setValue:@application/x-www-form-urlencoded forHTTPHeaderField:@Content-Type];
    [request setHTTPBody:postData];
    
    
    NSError *error = [[NSError alloc] init];
    NSHTTPURLResponse *response = nil;
    NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    
    NSLog(@Code de réponse : %ld, (long)[response statusCode]);
    
    if ([response statusCode] >= 200 && [response statusCode] < 300)
    {
        //NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
        //NSLog(@Réponse ==> %@", responseData);
        
        NSError *error = nil;
        NSArray* jsonData = [NSJSONSerialization
                                  JSONObjectWithData:urlData
                                  options:NSJSONReadingMutableContainers
                                  error:&error];
        NSLog(@jsonData: %@", jsonData);
        
        int cpt;
        
        listeContacts = [[NSMutableArray alloc]init];
        
        for(cpt=0; cpt<[jsonData count]; cpt++)
        {
            NSDictionary* dicoTemp = [jsonData objectAtIndex:cpt];
        
        NSMutableArray *arrayTemp = [[NSMutableArray alloc]init];
        [arrayTemp addObject:[dicoTemp objectForKey:@IDClient]];                        //0
        [arrayTemp addObject:[dicoTemp objectForKey:@IDContact]];                        //1
        [arrayTemp addObject:[dicoTemp objectForKey:@Nom]];                              //2
        [arrayTemp addObject:[dicoTemp objectForKey:@TelFixe]];                          //3
        [arrayTemp addObject:[dicoTemp objectForKey:@TelPortable]];                      //4
        [arrayTemp addObject:[dicoTemp objectForKey:@Email]];                           //5
        [arrayTemp addObject:[dicoTemp objectForKey:@Comm]];                            //6
        //[listeContacts addObject:arrayTemp];
        [listeContacts addObject:jsonData];
        NSLog(@Tab: %@", listeContacts);
        }
    }
    NSLog(@Tableau: %@", listeContacts);
}

Si une âme charitable pouvait m'aider ce serait cool, car je bloque depuis 1 journée sur ce souci.


 


Merci par avance ;)


Réponses

  • La méthode pour obtenir le nombre d'éléments d'un NSDictionary est -count, pas -length.


  • Oui j'ai bien compris ça, sauf que si tu regardes, j'ai bel et bien utilisé count et non length


     


    Les seuls endroits où j'ai utilisés length sont ici:


     


    NSString *postLength = [NSString stringWithFormat:@%lu, (unsigned long)[postData length]];

        [request setValue:postLength forHTTPHeaderField:@Content-Length];

     


  • CéroceCéroce Membre, Modérateur
    Regarde jsonData dans le débogueur.
    Tu as probablement un champ que tu penses être une chaà®ne, mais qui est un dictionnaire.
    (Ce n'est pas forcément ton code qui appelle -length).
  • AliGatorAliGator Membre, Modérateur
    octobre 2014 modifié #5

    La méthode pour obtenir le nombre d'éléments d'un NSDictionary est -count, pas -length.

    Oui ceci dit il n'appelle jamais length lui-même explicitement sur un NSDictionary... du moins ce n'est pas ce que son code donne l'impression de faire.

    Donc :
    • Soit un des appels à  la méthode "length" qu'il fait dans son code semble être sur une variable typée NSString ou NSData mais est en fait en vrai un NSDictionary (mais le seul appel que je vois c'est sur la variable "postData", qui, vu comment elle est créée est manifestement bien un NSData, donc le typage est correct)
    • Soit un des appels à  une autre méthode déclenche sous le capot l'appel à  la méthode length de façon "cachée" (par exemple peut-être que l'implémentation Apple de "objectAtIndex:" appelle "length" implicitement pour vérifier que l'index est dans le bon intervalle ?). Or manque de bol, l'objet sur lequel il appelle cette méthode " qu'il pense être un NSData ou un NSString ou un NSArray " est en réalité un NSDictionary
    Le cas 2 est le plus probable, étant donné qu'il désérialise du JSON avec NSJSONSerialization et met le résultat dans une variable jsonData... qu'il a typé dans le code comme étant un NSArray, mais si en réalité c'est un NSDictionary vu comment le JSON est structuré, bah forcément ça va pas le faire, il croit qu'il va appeler des méthodes sur un NSArray vu comment il a typé la variable, mais en réalité le vrai type de l'objet contenu dans cette variable est un NSDictionary.



    @Jean je te suggère de mettre un breakpoint juste après la ligne où tu affectes ton jsonData avec le résultat de NSJSONSerialization... et regarder le contenu de cette variable. Je suis prêt à  parier que si tu regardes son contenu (ou si tu log [jsonData class]) tu vas voir que c'est un NSDictionary et non pas un NSArray comme tu pensais. Ce qui expliquerais du coup le crash.

    Si ce n'est pas ça de toute façon il n'y a pas 36 façons de trouver le problème : tu mets un breakpoint et tu fais du pas à  pas, pour déterminer sur quelle ligne exactement l'exception intervient. Une fois que tu as déterminé la ligne qui fait crasher, tu regardes de plus près le contenu des variables en jeu sur cette ligne. Si ce n'est pas jsonData qui est un dico au lieu d'un NSArray, c'est peut-être sa structure qui n'est pas celle attendue, genre dans élément de jsonData à  un moment tu as un NSDictionary là  où tu t'attends à  autre chose...
  • samirsamir Membre
    octobre 2014 modifié #6

    Et au passage, relis le URL Loading System Programming guide https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html


     


    Et https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html


     


    Puisque la tu utilises du synchrone qui va te bloquer ton thread principal, surement tu va revenir plus tard avec une question " Mon interface se bloque :))"


     


    Rechercher dans le forum aussi, y a pleines de préconisations/recommandation à  ce sujet.


  • AliGatorAliGator Membre, Modérateur
    +1 samir.
    J'ai tjs pas compris pourquoi Apple laissait cette méthode synchrone dans l'API de NSURLConnection. Les requêtes synchrones, donc bloquant le main thread et donc l'interface, c'est le mal.
  • Jean75Jean75 Membre
    octobre 2014 modifié #8

    Négatif Céroce, mon jsonData est un tableau ;)


     



     


    jsonData: (


            {


            Comm = "<null>";


            Email = "";


            IDClient = 113;


            IDContact = 3;


            Nom = "DUPONT";


            TelFixe = "08 36 65 65 65";


            TelPortable = "";


        }


       ...


       {


       ...


        }


    )





  • Négatif Céroce, mon jsonData est un tableau ;)




    Justement le retour que tu obtiens est en fait un NSDictionnary.


    Le problème étant que tu le manipule comme une NSArray, qui ressemble au précédent mais qui ne l'est pas.


    Pour éviter de faire ce genre d'erreur tu peux faire quelque chose comme ça avant :



    if ([responseObject isKindOfClass:[NSArray class]]) {
    NSArray *responseArray = responseObject;
    /* do something with responseArray */
    NSLog(@Array);

    } else if ([responseObject isKindOfClass:[NSDictionary class]]) {
    NSDictionary *responseDict = responseObject;
    /* do something with responseDict */
    NSLog(@Dictionnary);

    }else{
    NSString *className = NSStringFromClass([responseObject class]);

    NSLog(@%@", responseObject);
    }
  • CéroceCéroce Membre, Modérateur

    J'ai tjs pas compris pourquoi Apple laissait cette méthode synchrone dans l'API de NSURLConnection.

    Pour que tu puisses créer toi-même le thread pour lancer la requête.
    Par exemple tu peux utiliser une NSOperation qui va appeler la méthode synchrone; ça t'offre plus de contrôle sur le nombres de requêtes simultanées, et permet de chaà®ner les appels plus facilement.
  • samirsamir Membre
    octobre 2014 modifié #11


    Pour que tu puisses créer toi-même le thread pour lancer la requête.




     


    Créer sans propre Thread pour la requête ne fait que consommer des resources en plus (RAM,..).


     




    Par exemple tu peux utiliser une NSOperation qui va appeler la méthode synchrone; ça t'offre plus de contrôle sur le nombres de requêtes simultanées




    Tu as cette flexibilité dans la NSOperationQueue. @property ;NSInteger maxConcurrentOperationCount. Ou bien tu tu veux dire autre chose ?


     


     




     et permet de chaà®ner les appels plus facilement.




    Tu peux chainer facilement aussi avec des dispatch_queue ou bien des NSOperationQueue. 


  • Ok MarcoDah.


     


    Du coup comment adapter mon code dans ce cas la s'il vous plait ?


     


    Désolé j'avoue être un peu perdu, du fait que je débute.


     


    Merci d'avance ;)


  • CéroceCéroce Membre, Modérateur
    @Samir: je réponds juste à  Ali qui demande à  quoi peuvent servir les méthodes synchrones de NSURLConnection.
  • Ben je sais pas trop ce que tu essais de faire mais un NSDictionnary est une (sorte) de array grâce auquel tu peux accéder aux valeurs via des clés.


     


    Les clés sont définis par ton fichier JSON. Autrement dit ici si tu veux accéder à  la valeur de la clé 'nom' il suffit de faire:



    NSString * nom = jsonData['Nom']; // Ca te retournera "DUPONT"
  • NasatyaNasatya Membre
    octobre 2014 modifié #16

    Je vois pas trop ce que tu veux changer j'ai copié ton fichier json sur mon bureau créer une map local avec "charles" pour pouvoir le récupérer facilement sur mon application j'ai copié collé ton code et enlever la partie POST qui pour mon cas de test était du coup inutile et le code compile très bien j'ai bien les données qui remonte dans les logs.


     


    Je te donne le JSON et le code pour que tu puisse comparer j'ai vraiment pas changé grand chose. 



    //Le json que j'ai juste adapter au format json pour que mon appli puisse l'utiliser
    [
    {
    "Comm":"<null>",
    "Email":"",
    "IDClient":113,
    "IDContact":3,
    "Nom":"DUPONT",
    "TelFixe":"08 36 65 65 65",
    "TelPortable":""
    }
    ]


    //La fonction pour Xcode j'ai juste enlevé le surplus j'ai rien modifié.
    -(void)getInfosContact
    {
    NSURL *url=[NSURL URLWithString:@http://www.monadresse.com/contact.php];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:url];


    NSError *error = [[NSError alloc] init];
    NSHTTPURLResponse *response = nil;
    NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    NSLog(@Code de réponse : %ld, (long)[response statusCode]);

    if ([response statusCode] >= 200 && [response statusCode] < 300)
    {
    NSError *error = nil;
    NSArray* jsonData = [NSJSONSerialization
    JSONObjectWithData:urlData
    options:NSJSONReadingMutableContainers
    error:&error];
    NSLog(@jsonData: %@", jsonData);

    int cpt;

    NSMutableArray *listeContacts = [[NSMutableArray alloc] init];

    for(cpt=0; cpt<[jsonData count]; cpt++)
    {
    NSDictionary* dicoTemp = [jsonData objectAtIndex:cpt];

    NSMutableArray *arrayTemp = [[NSMutableArray alloc]init];
    [arrayTemp addObject:[dicoTemp objectForKey:@IDClient]]; //0
    [arrayTemp addObject:[dicoTemp objectForKey:@IDContact]]; //1
    [arrayTemp addObject:[dicoTemp objectForKey:@Nom]]; //2
    [arrayTemp addObject:[dicoTemp objectForKey:@TelFixe]]; //3
    [arrayTemp addObject:[dicoTemp objectForKey:@TelPortable]]; //4
    [arrayTemp addObject:[dicoTemp objectForKey:@Email]]; //5
    [arrayTemp addObject:[dicoTemp objectForKey:@Comm]]; //6
    //[listeContacts addObject:arrayTemp];
    [listeContacts addObject:jsonData];
    NSLog(@Tab: %@", listeContacts);
    }
    NSLog(@Tableau: %@", listeContacts);
    }

    }



    //Les log retournés
    2014-10-31 08:23:34.602 AppTest[584:60b] Code de réponse : 200
    2014-10-31 08:23:34.602 AppTest[584:60b] jsonData: (
    {
    Comm = "<null>";
    Email = "";
    IDClient = 113;
    IDContact = 3;
    Nom = DUPONT;
    TelFixe = "08 36 65 65 65";
    TelPortable = "";
    }
    )
    2014-10-31 08:23:34.603 AppTest[584:60b] Tab: (
    (
    {
    Comm = "<null>";
    Email = "";
    IDClient = 113;
    IDContact = 3;
    Nom = DUPONT;
    TelFixe = "08 36 65 65 65";
    TelPortable = "";
    }
    )
    )
    2014-10-31 08:23:34.603 AppTest[584:60b] Tableau: (
    (
    {
    Comm = "<null>";
    Email = "";
    IDClient = 113;
    IDContact = 3;
    Nom = DUPONT;
    TelFixe = "08 36 65 65 65";
    TelPortable = "";
    }
    )

    Du coup il faudrait que tu regardes dans le reste du code je penses parce que la je penses pas qu'il y ai d'erreur.


    Bon courage.


  • Hello Marco et Nasa, merci de chercher à  m'aide.


     


    Effectivement Nasa ton code est quasiment le même que celui que j'ai fait.


     


    Du coup je pense que l'erreur viens de la, vu qu'elle viens pas de la méthode



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *simpleTableIdentifier = @MaCellule;

    [self getInfosContact];

    MyTableViewCell *cell = [tabView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

    if (cell == nil) {
    cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
    }

    cell.typeContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:1];
    cell.nomPrenomContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:2];
    cell.telFixeContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:3];
    cell.telPortableContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:4];
    cell.emailContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:5];

    //cell.textLabel.text = [listeContacts objectAtIndex:indexPath.row];
    return cell;
    }

    J'aimerais récupérer les valeurs du dictionnaire qui se trouve dans mon tableau d'ou la logique de vouloir faire d'abord prendre la cellule 0 (puis 1, puis 2, etc.) et de vouloir récupérer ensuite les différentes infos d'ou le fait de prendre l'object a l'index n.


     


    Mais je présume que c'est ici que ça pose problème :/


  • Tu es pas censé avoir un dictionnaire à  un moment donnée?



    //Ici tu cherches dans un array puis dans un array
    cell.typeContact.text=[[listeContacts objectAtIndex:indexPath.row]objectAtIndex:1];

    //Ic tu cherche dans un array puis dans un dictionary c'est pas plutôt ça que tu veux faire?
    cell.typeContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@IDContact];

    Si c'est pas ça je vois pas trop ce que ça peut être.


  • NasatyaNasatya Membre
    octobre 2014 modifié #19

    Ok en fait tu te prend vraiment la tête pour rien je viens de comprendre ton code c'est un peu tiré par les cheveux.



    -(void)getInfosContact
    {
    NSURL *url=[NSURL URLWithString:@http://www.monadresse.com/contact.php];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:url];


    NSError *error = [[NSError alloc] init];
    NSHTTPURLResponse *response = nil;
    NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    NSLog(@Code de réponse : %ld, (long)[response statusCode]);

    if ([response statusCode] >= 200 && [response statusCode] < 300)
    {
    NSError *error = nil;
    self.listeContacts = [NSJSONSerialization
    JSONObjectWithData:urlData
    options:NSJSONReadingMutableContainers
    error:&error];;
    NSLog(@%@", [self.listeContacts objectAtIndex:0]);
    }

    }

    et dans ton cellforrow il te faudrait un truc du genre



    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *simpleTableIdentifier = @MaCellule;

    [self getInfosContact];

    MyTableViewCell *cell = [tabView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

    if (cell == nil) {
    cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
    }

    cell.typeContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@taKey];
    cell.nomPrenomContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@taKey];
    cell.telFixeContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@taKey];
    cell.telPortableContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@taKey];
    cell.emailContact.text=[[listeContacts objectAtIndex:indexPath.row] objectForKey:@taKey];

    //cell.textLabel.text = [listeContacts objectAtIndex:indexPath.row];
    return cell;
    }

    J'avais pas vu le truc complètement alambiqué d'aller mettre un dictionnaire dans un array pour ensuite aller l'ajouter dans un array pour aller plus tard le lire dans un tableView ça parait pas simple comme façon de faire.


     


    Pour répondre a ton souci et te dire pourquoi tu avais un bug en fait tu essais tout simplement d'inserer un dictionnaire dans un champs texte et du coup il ne veut pas ce qui parait quand même normal.


  • Bonjour, merci beaucoup Nasa, ça fonctionne niquel.


     


    Dernière petite question:


     


    Je doit convertir ma première clé en int, du coup j'ai tout de suite pensé à  une méthode du genre intValue qui n'existe pas. Saurais tu me dire l'équivalent à  cela s'il te plait.


     


    J'aimerais faire quelque chose comme ça en fait:



    cell.typeContact.text=[_typeContact objectAtIndex:[[listeContacts objectAtIndex:indexPath.row]objectForKey:@IDContactNature]];

    Cette première me renvoie des valeurs comme 1, 2 ou 23 mais je présume que ce sont des valeurs texte


  • Ben si la fonction intValue exite par contre si tu veux l'appliquer sur une fonction entre crochet il faut bien penser à  ouvrir le crochet de départ pour que Xcode te propose l'auto-complétion.


  • Effectivement merci, je pensais que même sans les crochets il me l'aurait proposé.


     


    Problème réglé, milles merci à  tout ceux m'ayant aidé


  • AliGatorAliGator Membre, Modérateur
    octobre 2014 modifié #23



    cell.typeContact.text = [_typeContact objectAtIndex:[[[listeContacts objectAtIndex:indexPath.row]objectForKey:@IDContactNature] intValue]];



    1) Dans des conteneurs comme NSDictionary ou NSArray on ne peut pas mettre des types primitifs comme int ou float. On est obligés de les encapsuler dans des objets NSNumber (qui est juste un wrapper objet autour d'un nombre pour justement pouvoir le mettre dans des conteneurs ensuite). NSJSONSerialization encapsule tout seul les nombres qu'il trouve dans ton JSON en objets NSNumber pour toi.


     


    Du coup finalement si tu appelles intValue à  la fin c'est bien la méthode intValue venant de la classe NSNumber donc que tu appelles ici, puisque tu récupères donc un NSNumber en allant fouiller dans ton NSDictionary.


     


    2) Sache qu'il existe une notation plus moderne (et surtout plus courte à  écrire) pour objectAtIndex de NSArray comme pour objectForKey de NSDictionary : les crochets ; tu peux ainsi écrire ton code ci-dessus de manière plus condensée comme cela :



    cell.typeContact.text = _typeContact[  [listeContacts[indexPath.row][@IDContactNature] intValue]  ];

  • Alors attend j'étais au boulot j'avais pas lu tout ton poste tu vas avoir un array avec comme index 1, 2, 23? ça me parait pas être super indexé cette histoire? Alors de un je penses qu'il y a des meilleurs méthode pour gérer ça. Ensuite ça me parait effectivement être plus un dictionnaire qu'un array bien que les deux se ressemble quand même beaucoup.


     


    Pour la gerstion je penses à  un webservice ou autre qui renverrais l'objet unique avec l'identifiant du genre 


     


    + (TypeContact *)typeContactWithId:(NSNumber *)idTypeContact


     


    Dans un objet TypeContact qui serait un modèle de ton webService et qui donc ferais l'interface entre tes données sur internet et tes données sur l'application 


     


    Ensuite tu généralise ce système avec ListeContact tu mets toute la gestion des array dictionary et autre dans ton modèle et dans ton code tu auras une ligne du genre 



    cell.typeContact.text = [[TypeContact typeContactWithId:listeContact.typeContactId] libelle];

    Donc ça parait pas folichon comme ça mais faire des modèles même pour les petit webservice ça évite bien souvent de faire des recopie de code de partout ça facilite donc énormément les évolutions, la maintenance, et bien souvent ça allège quand même beaucoup le code des contrôleurs.


     


    Après je dis pas que c'est pas possible de faire comme tu fais je dis juste que c'est plus sujet à  bug et à  galère éventuelle plus tard.


Connectez-vous ou Inscrivez-vous pour répondre.