Boucler dans un NSDictionary rechargées depuis la liste des propriétés Prooving Troublesome
-
28-09-2019 - |
Question
les gars Ey, je lis depuis un plist structuré comme ceci:
Comme vous pouvez le voir, il est un plist qui contient des informations d'annotation de différents types (C, Visiteur). Je peux afficher chaque type d'annotation très bien, mais je tente de boucle à travers tous les types et afficher toutes les annotations sur la vue de la carte à la fois. Voici le code:
NSLog(@"loadAnnotations");
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"PermitData" ofType:@"plist"];
NSDictionary *rootOfPermitDataPlistDict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
// NSMutableDictionary *permitDict = [[NSMutableDictionary alloc] init];
if ([self title] == @"All Permits") {
for (id key in rootOfPermitDataPlistDict) {
NSLog(@"key:%@",key);
//[key retain];
NSMutableDictionary *permitDict = [NSDictionary dictionaryWithDictionary:[rootOfPermitDataPlistDict objectForKey:key]];
//[key release];
//array containing annotation information: latitude, longitude, title, subtitle(see PermitData.plist)
NSArray *annotationsArray = [[NSArray alloc] initWithArray:[permitDict objectForKey:@"annotations"]];
[permitDict release];
[rootOfPermitDataPlistDict release];
CLLocationCoordinate2D workingCoordinate;
NSDictionary *annotationContainerDict = [[NSDictionary alloc] init];
//loop through annotations array, creating parking annotations filled with the information found in the plist
for(annotationContainerDict in annotationsArray){
NSLog(@"%@",annotationContainerDict);
ParkingAnnotation *parkingAnnot = [[ParkingAnnotation alloc] init];
workingCoordinate.latitude = [[annotationContainerDict objectForKey:@"latitude"] doubleValue];
workingCoordinate.longitude = [[annotationContainerDict objectForKey:@"longitude"] doubleValue];
[parkingAnnot setCoordinate:workingCoordinate];
[parkingAnnot setTitle:[annotationContainerDict objectForKey:@"title"]];
[parkingAnnot setSubtitle:[annotationContainerDict objectForKey:@"subtitle"]];
if ([parkingAnnot title] == @"C Parking") [parkingAnnot setAnnotationType:annotationTypeC];
else if ([parkingAnnot title] == @"Visitor Parking") [parkingAnnot setAnnotationType:annotationTypeVisitor];
[mapView addAnnotation:parkingAnnot];
[parkingAnnot release];
}
[permitDict release];
}
}
Et ceci est la sortie de la console quand je lance le programme:
2010-11-25 03:25:28.020 Parking[38918:207] All Permits
2010-11-25 03:25:28.021 Parking[38918:207] loadAnnotations
2010-11-25 03:25:28.021 Parking[38918:207] key:C
2010-11-25 03:25:28.021 Parking[38918:207] {
latitude = "38.545301";
longitude = "-121.754066";
subtitle = "VP 17";
title = "C Parking";
}
2010-11-25 03:25:28.022 Parking[38918:207] {
latitude = "38.544831";
longitude = "-121.754785";
subtitle = "VP 16";
title = "C Parking";
}
2010-11-25 03:25:28.022 Parking[38918:207] {
latitude = "38.544781";
longitude = "-121.755729";
subtitle = "VP 22";
title = "C Parking";
}
2010-11-25 03:25:28.022 Parking[38918:207] {
latitude = "38.544412";
longitude = "-121.752489";
subtitle = "VP 15";
title = "C Parking";
}
Il boucle à travers la première NSDictionary correctement, mais tombe en panne quand il est sur le point de commencer une boucle à travers la suivante. Et je l'ai essayé de changer NSDictionary à NSMutableDictionary mais le résultat est le même. Ainsi, lorsque je sélectionne la ligne « Tous les permis » dans l'affichage de la table, il se bloque pendant une fraction de seconde (sans passer à la vue en plan), puis se bloque, sans générer une erreur.
Si quelqu'un ne me dérangerait pas de me aider ici, je serais très heureux. Merci à l'avance!
EDIT: Voici la trace de la pile (décrite dans les commentaires ci-dessous):
2010-11-25 20:28:08.141 Parking[39400:207] All Permits
2010-11-25 20:28:08.142 Parking[39400:207] loadAnnotations
2010-11-25 20:28:08.142 Parking[39400:207] key:C
2010-11-25 20:28:08.143 Parking[39400:207] {
latitude = "38.545301";
longitude = "-121.754066";
subtitle = "VP 17";
title = "C Parking";
}
2010-11-25 20:28:08.143 Parking[39400:207] {
latitude = "38.544831";
longitude = "-121.754785";
subtitle = "VP 16";
title = "C Parking";
}
2010-11-25 20:28:08.143 Parking[39400:207] {
latitude = "38.544781";
longitude = "-121.755729";
subtitle = "VP 22";
title = "C Parking";
}
2010-11-25 20:28:08.144 Parking[39400:207] {
latitude = "38.544412";
longitude = "-121.752489";
subtitle = "VP 15";
title = "C Parking";
}
2010-11-25 20:28:08.145 Parking[39400:207] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFDictionary: 0x6d5fd80> was mutated while being enumerated.<CFBasicHash 0x6d5fd80 [0x2667380]>{type = mutable dict, count = 1,
entries =>
0 : <0x7380> = <NSKeyValueContainerClass: Original class: ParkingAnnotation, Notifying class: NSKVONotifying_ParkingAnnotation>
}
'
*** Call stack at first throw:
(
0 CoreFoundation 0x025fdb99 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x0274d40e objc_exception_throw + 47
2 CoreFoundation 0x025fd659 __NSFastEnumerationMutationHandler + 377
3 Parking 0x00002e93 -[ParkingMapViewController loadAnnotations] + 364
4 Parking 0x00002caf -[ParkingMapViewController viewDidLoad] + 117
5 UIKit 0x0036a5ca -[UIViewController view] + 179
6 UIKit 0x003689f4 -[UIViewController contentScrollView] + 42
7 UIKit 0x003787e2 -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] + 48
8 UIKit 0x00376ea3 -[UINavigationController _layoutViewController:] + 43
9 UIKit 0x00378067 -[UINavigationController _startTransition:fromViewController:toViewController:] + 326
10 UIKit 0x00372ccd -[UINavigationController _startDeferredTransitionIfNeeded] + 266
11 UIKit 0x00379d8b -[UINavigationController pushViewController:transition:forceImmediate:] + 876
12 UIKit 0x00372b67 -[UINavigationController pushViewController:animated:] + 62
13 Parking 0x00002914 -[PermitListViewController tableView:didSelectRowAtIndexPath:] + 307
14 UIKit 0x00333a48 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1140
15 UIKit 0x0032a32e -[UITableView _userSelectRowAtIndexPath:] + 219
16 Foundation 0x0003f21a __NSFireDelayedPerform + 441
17 CoreFoundation 0x025def73 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 19
18 CoreFoundation 0x025e05b4 __CFRunLoopDoTimer + 1364
19 CoreFoundation 0x0253cdd9 __CFRunLoopRun + 1817
20 CoreFoundation 0x0253c350 CFRunLoopRunSpecific + 208
21 CoreFoundation 0x0253c271 CFRunLoopRunInMode + 97
22 GraphicsServices 0x02edc00c GSEventRunModal + 217
23 GraphicsServices 0x02edc0d1 GSEventRun + 115
24 UIKit 0x002ceaf2 UIApplicationMain + 1160
25 Parking 0x00001e08 main + 102
26 Parking 0x00001d99 start + 53
)
terminate called after throwing an instance of 'NSException'
Cependant, je publie permitDict au sein de la boucle (deuxième [version permitDict] a été laissé décommentée), donc je ne sais pas pourquoi il se plaint de l'NSDictionary étant immuable. Comme vous pouvez le voir, le permitDict est de type NSMutableDictionary, donc je suis perdu la raison pour laquelle il me donne cette erreur.
La solution
Le code se bloque pour deux raisons principales.
Première , cette ligne qui est à l'intérieur de la boucle for:
[rootOfPermitDataPlistDict release];
détruit l'objet même que vous êtes en train de bouclage. Déplacer à la fin -. Après l'accolade de clôture de l'instruction if ([self title]...
Deuxième , les deux lignes qui disent:
[permitDict release];
doit être supprimé. Ne relâchez pas permitDict parce que vous créez à l'aide dictionaryWithDictionary qui retourne un objet autoreleased.
Avec ces deux changements, le code doit être exécuté.
Cependant , il y a quelques autres problèmes:
- Vous alloc + initialisation
annotationsArray
mais jamais le libérer (fuite de mémoire). Relâchez après la boucle for qui passe par ce tableau (où vous avez actuellement la deuxième[permitDict release];
). - Vous alloc + initialisation
annotationContainerDict
mais utiliser la variable uniquement comme référence aux objets dans le annotationsArray (et donc l'abandon de la mémoire allouée - fuite de mémoire). Ne vous embêtez pas faire alloc + init annotationContainerDict, déclarer juste. ChangementNSDictionary *annotationContainerDict = [[NSDictionary alloc] init];
justeNSDictionary *annotationContainerDict;
. - Vous comparez des chaînes en utilisant
==
. UtilisezisEqualToString:
plutôt comme ceci:
if ([[self title] isEqualToString:@"All Permits"])...
. Pour une explication des raisons pour lesquelles vous devriez utiliserisEqualToString
, voir cette question et cette question. - Pas vraiment un problème grave ici, mais il est inutile d'utiliser dictionaryWithDictionary et dupliquer ainsi ce qui est déjà là dans rootOfPermitDataPlistDict. Vous pouvez simplement utiliser permitDict comme référence de raccourci au dictionnaire intégré comme ceci:.
NSMutableDictionary *permitDict = [rootOfPermitDataPlistDict objectForKey:key];
- Même point avec annotationsArray: vous ne devez alloc + initialiser un nouveau tableau. Le tableau est déjà en rootOfPermitDataPlistDict. Procurez-vous une référence à comme ceci:
NSArray *annotationsArray = [permitDict objectForKey:@"annotations"];
. Ne relâchez pas annotationsArray si vous décidez de le faire de cette façon.
Le Guide de programmation Gestion de la mémoire explique tout cela en détail.
Autres conseils
Tout d'abord, il y avait beaucoup de problèmes avec votre code, mais une seconde (ce qui pourrait créer de nombreux objets autoreleased) à moins que cela est la performance du code critique, où vous serez mise en boucle des centaines de fois, en utilisant la méthode alloc] init] contenterai faire plus difficile à suivre le code. (Puisque vous devrez mentalement passer et équilibrer en permanence un alloc avec une sortie). Ne vous méprenez pas, il est important que vous compreniez conserver et de la libération, etc., mais c'est la façon dont j'aborder le problème.
Rappelez-vous que vous êtes en contrôle. Faites vos classes d'annotations de stationnement pouvoir « penser pour eux-mêmes » un peu. Dans l'exemple ci-dessous, j'ai ajouté un - (id) initWithDictionary: pour que votre autre classe ne doit pas rester là et faire tout le travail grognement de réglage des touches une par une. (Il y a certaines parties de fiction où je suppose que vous souhaitez remplir les espaces vides ...
NSString * const PPAnnotationsKey = @"annotations";
NSString * const PPTitleKey = @"title";
NSString * const PPSubtitleKey = @"subtitle";
NSString * const PPLatitudeKey = @"latitude";
NSString * const PPLongitudeKey = @"longitude";
NSLog(@"loadAnnotations");
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"PermitData" ofType:@"plist"]; // autoreleased
NSDictionary *permitDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath]; // autoreleased
if ([[self title] isEqualToString:@"All Permits"]) {
for (NSString *parkingGroup in permitDictionary) {
NSLog(@"parkingGroup == %@", parkingGroup);
NSArray *annotations = [parkingGroup objectForKey:PPAnnotationsKey];
for (NSDictionary *entry in annotations) {
PPParkingAnnotation *annotation = [PPParkingAnnotation parkingAnnotationWithDictionary:entry]; // autoreleased
if (annotation) {
// assuming here that mapView's addAnnotation: will retain the
// annotation
[mapView addAnnotation:annotation];
}
}
}
}
@interface PPParkingAnnotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
+ (id)parkingAnnotationWithDictionary:(NSDictionary *)dictionary;
- (id)initWithDictionary:(NSDictionary *)dictionary;
@properties...
@end
@implementation PPParkingAnnotation
+ (id)parkingAnnotationWithDictionary:(NSDictionary *)dictionary {
return [[[[self class] alloc] initWithDictionary:dictionary] autorelease];
}
- (id)initWithDictionary:(NSDictionary *)dictionary {
[self setTitle:[dictionary objectForKey:PPTitleKey]];
[self setSubtitle:[dictionary objectForKey:PPTitleKey]];
// and so on.
}