2009-12-10 6 views
2

J'ai commencé à utiliser le framework ObjectiveFlickr dans le but de créer une application de carte iPhone relativement simple montrant le contenu flickr géolocalisé dans la région MKMapView actuelle. Je me suis heurté à des problèmes liés au filetage before et maintenant j'ai le sentiment que je reçois quelque chose de fondamentalement faux dans mon architecture. En fait ce que j'ai est:Le bon endroit pour faire les choses en Objective-C

  1. Un MainViewController qui crée et gère l'objet MKMapView et un bouton
  2. En appuyant sur le bouton appelle une méthode qui appelle l'API Flickr photos géolocalisées dans la carte actuelle ampleur.
  3. La méthode de rappel pour cet appel d'API itère à travers les résultats et les place dans un NSMutableArray d'objets FlickrImage. FlickrImage est une classe de données simple contenant l'emplacement de l'image flickr sous la forme d'une CLLocation, d'un NSURL pointant sur la vignette et d'un titre NSString.

extrait de code pour l'étape 2:

-(void)actionSearchForTripodPhotos { 
    if(currentBoundingBox == nil) { 
     // TODO add a messagebox saying we're waiting for location info - or just lock the app until we're sure. 
     return; 
    } 
    NSString *dateTakenMinimumUNIXTimeStampString = [NSString stringWithFormat:@"%f",[[NSDate dateWithTimeIntervalSinceNow:-100000] timeIntervalSince1970]]; 
    OFFlickrAPIRequest *flickrAPIRequest = [[OFFlickrAPIRequest alloc] initWithAPIContext:[CloudMadeMap101AppDelegate sharedDelegate].flickrAPIContext]; 
    [flickrAPIRequest setDelegate:self]; 
    NSString *flickrAPIMethodToCall = @"flickr.photos.search"; 
    NSString *bboxString = [NSString stringWithFormat:@"%f,%f,%f,%f",currentBoundingBox.bottomLeftLat ,currentBoundingBox.bottomLeftLon ,currentBoundingBox.topRightLat ,currentBoundingBox.topRightLon]; 
    NSLog(@"bounding box to be sent to flickr: %@",bboxString); 
    NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,@"api_key",[NSString stringWithFormat:@"%f",currentLocation.coordinate.latitude],@"lat",[NSString stringWithFormat:@"%f",currentLocation.coordinate.longitude],@"lon",dateTakenMinimumUNIXTimeStampString,@"min_upload_date",nil]; 
    [flickrAPIRequest callAPIMethodWithGET:flickrAPIMethodToCall arguments:requestArguments]; 
} 
extrait de code

pour l'étape 3:

- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse:(NSDictionary *)inResponseDictionary { 
NSDictionary *photosDictionary = [inResponseDictionary valueForKeyPath:@"photos.photo"]; 
NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 
    NSLog(@"photodictionary is %@",[photoDictionary description]); 
    flickrImage = [[FlickrImage alloc] init]; 
    flickrImage.thumbnailURL = [[appDelegate sharedDelegate].flickrAPIContext photoSourceURLFromDictionary:photoDictionary size:OFFlickrThumbnailSize]; 
    flickrImage.hasLocation = TRUE; // TODO this is actually to be determined... 
    flickrImage.ID = [NSString stringWithFormat:@"%@",[photoDictionary valueForKeyPath:@"id"]]; 
    flickrImage.owner = [photoDictionary valueForKeyPath:@"owner"]; 
    flickrImage.title = [photoDictionary valueForKeyPath:@"title"]; 
    [flickrImages addObject:flickrImage]; 
    [photoDictionary release];   
} 
} 

Ce tout va bien. L'API ne renvoie pas de géolocalisation pour chaque photo, ce qui nécessite un autre appel d'API. Je pensais que je pourrais le faire à partir de la classe FlickrImage, mais ici ça devient laid:

  • Le MainViewController crée une instance de FlickrImage chaque itération et le stocke dans le NSMutableArray.
  • L'instance FlickrImage appelle de façon asynchrone l'API Flickr de géolocalisation et doit stocker les coordonnées renvoyées dans la variable membre appropriée.

Je suis assez sûr que cela ne se produit pas parce que je reçois

malloc: *** error for object 0x451bc04: incorrect checksum for freed object - object was probably modified after being freed. 

saupoudré autour de ma sortie de débogage, et presque toujours un EXC_BAD_ACCESS mais pas toujours au même point.

Je fais clairement quelque chose de fondamentalement faux ici, mais quoi?

+0

Avez-vous une idée de la destination de l'objet 0x452bc04? Si vous consignez les adresses de vos objets, en particulier les objets FlickrImage, vous pouvez alors regarder l'adresse du débogueur et voir au moins quelle classe a échoué. – TechZen

+0

Vous devez coller votre code des deux dernières étapes (création, stockage et modification de FlickrImages). –

+0

@ kai1968 - extraits de code ajoutés – mvexel

Répondre

2

Lorsque vous itérer sur le dictionnaire il n'y a pas besoin d'appeler [photoDictionary release]:

NSDictionary *photosDictionary = 
     [inResponseDictionary valueForKeyPath:@"photos.photo"]; 
NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 
    ... 
    [photoDictionary release];    

Je pense que c'est là votre problème.

Lorsque vous appelez release et que l'objet atteint ref count 0, il est désalloué. Comme vous n'étiez pas censé faire cela, plus tard, lorsque le dictionnaire est publié, il envoie release à chacun de ses éléments mais vous les avez peut-être déjà désalloués.

Ceci est la gestion de la mémoire de base dans l'objectif-c. Jetez un oeil à la gestion de la mémoire et retain/release/autorelease choses pour plus d'explications.

+0

Merci stefanB, d'autres l'ont également souligné, mais je vous remercie d'avoir élaboré sur le sujet. Je suis sûr de lire plus sur la gestion de la mémoire, c'est certainement l'un des points faibles de mes compétences ObjC - n'ayant pas d'expérience en C/C++ mais seulement dans les langages de collecte de déchets comme Java et .NET je ne suis pas habitué à devoir s'en préoccuper. – mvexel

+0

La gestion de la mémoire est en fait assez simple, elle devient seulement confuse lorsque vous essayez de concevoir l'interaction entre les composants et vous perdez la trace de qui possède quoi. Objective-c ajoute en fait une très bonne gestion du comptage des références avec des trucs retenir/libérer/autorelease. Si vous recherchez la gestion de la mémoire dans les spécifications Apple, vous trouverez quelques règles simples à suivre qui vous expliquera comment utiliser la mémoire du gestionnaire par rapport à l'utilisation de Cocoa. – stefanB

6

L'erreur signifie exactement ce qu'elle dit: vous avez probablement modifié la mémoire après l'avoir libérée, et EXC_BAD_ACCESS indique que vous essayez d'accéder à un élément de tableau qui n'existe pas. Je vérifierais que vos objets FlickrImage ne sont pas désaffectés au moment où vous essayez d'appeler la méthode de géolocalisation.

Il n'y a rien dans votre conception qui se démarque comme étant fondamentalement défectueux

+3

+1. Et utilisez NSZombieEnabled pour savoir quel objet est le coupable. – diciu

+0

@ennuikiller: merci pour le réconfort;) C'est ce que je * crois * qui se passe en effet, et j'aimerais le vérifier, mais comment puis-je le faire? – mvexel

+0

J'ai ajouté quelques extraits de code en passant, cela pourrait clarifier les choses. Merci! – mvexel

2

@techzen - c'est là que je manque de compétences Xcode /gdb. Comment puis-je les enregistrer? Ce fournirait un aperçu utile en effet.

Dans le cas de classes inhérentes à NSObject, vous pouvez simplement faire en sorte que NSLog imprime directement l'objet.

NSLog(@"myObject=%@", myObjectInstance); 

NSLog appellera la méthode des instances de debugDescription qui habituellement imprimer quelque chose comme:

<MyObjectClass 0x451bc04> 

Certaines classes produisent beaucoup plus d'informations, mais parfois ils ne fournissent pas les instances adresse que vous devez imprimez-le directement:

Le spécificateur de format "% p" est l'astuce. Vous voulez sans doute étoffer un peu comme:

NSLog(@"<%@ %p>", [myObjectInstance class], myObjectInstance); 
// prints <MyObjectClass 0x451bc04> 

Mettez ces déclarations dans des endroits où vous créez des objets et lorsque l'erreur se produit, vous pouvez voir ce que les objets échoué en regardant dans la console de débogage.

+0

Merci pour ces précieux conseils. Je pense que ce type de matériel n'est pas si bien couvert dans la documentation de développement d'Apple, ou est-ce que je néglige les choses ici? – mvexel

1

Je pense que le problème principal est que votre boucle d'énumération rapide n'est pas correctement configurée pour un dictionnaire. Contrairement aux tableaux, l'énumération rapide dans un dictionnaire ne renvoie que des clés et non des valeurs. par exemple.

NSDictionary *a=[NSDictionary dictionaryWithObjectsAndKeys:@"bob",@"bobKey",@"steve",@"steveKey",nil]; 
NSDictionary *b=[NSDictionary dictionaryWithObjectsAndKeys:@"bob1",@"bobKey",@"steve1",@"steveKey",nil]; 
NSDictionary *c=[NSDictionary dictionaryWithObjectsAndKeys:a,@"a",b,@"b",nil]; 
NSDictionary *d; 
for (d in c) { 
    NSLog(@"[d class]=%@,[d description]=%@",[d class],d); 
    NSLog(@"[c valueForKey:d]=%@",[c valueForKey:d]); 
} 

impressions:

[d class]=NSCFString,[d description]=a 
[c valueForKey:d]={ 
    bobKey = bob; 
    steveKey = steve; 
} 

[d class]=NSCFString,[d description]=b 
[c valueForKey:d]={ 
    bobKey = bob1; 
    steveKey = steve1; 
} 

Notez que même si d est défini comme un NSDictionary il est toujours désigné comme la valeur de chaîne des clés.

Vous devez changer:

NSDictionary *photoDictionary; 
FlickrImage *flickrImage; 
for (photoDictionary in photosDictionary) { 

à quelque chose comme:

NSString *dictKey; 
NSDictionary *photoDictionary; 
for (dictKey in photosDictionary) { 
    photoDictionary=[photosDictionary valueForKey:dictKey]; 
    ... 

et vous n'avez pas besoin de libérer photoDictionary parce que vous ne créez pas un nouvel objet que vous venez d'obtenir une référence à lui.

+0

Hm, point intéressant. Dans mon code actuel cependant, le NSLog (@ "photodictionary est% @", [description photoDictionary]); génère une description d'objet de dictionnaire apparemment valide, par exemple: photodictionary est { farm = 3; id = 4173994142; isfamily = 0; isfriend = 0; ispublic = 1; owner = "37727710 @ N08"; secret = 75f2885f80; serveur = 2521; title = "Dit est een kunstwerk"; } – mvexel

+0

Avez-vous les clés de votre photosdictionary sur des objets photodictionnaire? C'est facile à faire si vous inversez vos clés et vos valeurs. Vous devriez enregistrer la valeur return par '[photosDictionary valueForKey: photodictionary]' et voir ce que vous obtenez. – TechZen

Questions connexes