I have a tableview with a navigationBar with a segmentedControl on the top of the view. I have set up the segmentedControl with buttons that sort the table by either "FirstName" or "LastName". It works perfectly the first 2-4 of times you press the sorting buttons, but then the app crashes.
The debugger and console seem to be of no help finding the source of the bug. Does anyone see any glaring mistakes in my code?
Here is my code below, let me know if you have any questions. Thanks!
- (IBAction)sortingSegmentAction:(id)sender{
NSString *keyToSortBy = [NSString alloc];
if([sender selectedSegmentIndex] == 0)
{
self.sortingSegmentActionPressed = 0;
keyToSortBy = #"FirstName";
}
else if([sender selectedSegmentIndex] == 1)
{
self.sortingSegmentActionPressed = 1;
keyToSortBy = #"LastName";
}
//Create the sort descriptors
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:keyToSortBy ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
//Sort allSubItams by the values set in the sort descriptors
NSArray *sortedArray;
self.sortedArray = [allSubItems sortedArrayUsingDescriptors:sortDescriptors];
//Recreate the data structure by putting the newly sorted items into a dictionary sorted by inital letters.
NSDictionary *eachItemList; //A DICTIONARY FOR PUTTING ALL THE DATA FOR EACH ITEM IN IT'S OWN SECTION
NSMutableDictionary *tempSectionedDictionaryByFirstLetter = [[NSMutableDictionary alloc] init];
for (eachItemList in sortedArray) //eachElementList is a dictionary with a section for each item
{
NSDictionary *aDictionary = [[NSDictionary alloc] initWithDictionary:eachItemList];
NSString *firstLetterString;
firstLetterString = [[aDictionary valueForKey:keyToSortBy]substringToIndex:1];
NSMutableArray *existingArray;
if (existingArray = [tempSectionedDictionaryByFirstLetter valueForKey:firstLetterString])
{
[existingArray addObject:eachItemList];
} else {
NSMutableArray *tempArray = [NSMutableArray array];
[tempSectionedDictionaryByFirstLetter setObject:tempArray forKey:firstLetterString];
[tempArray addObject:eachItemList];
}
[aDictionary release];
[eachItemList release];
}
//Set the data source for the table (sectionedDictionaryByFirstLetter) to tempSectionedDictionaryByFirstLetter.
self.sectionedDictionaryByFirstLetter = tempSectionedDictionaryByFirstLetter;
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
[keyArray addObjectsFromArray:[[self.sectionedDictionaryByFirstLetter allKeys] sortedArrayUsingSelector:#selector(compare:)]];
self.keys = keyArray;
[self.tableView reloadData];
[keyArray release];
[tempSectionedDictionaryByFirstLetter release];
}
Don't release eachItemList at the end of your loop. You did not explicitly allocate it in this context so you shouldn't release it.
The for (object in array) loop gives you a reference to the object in the array, not a copy. By sending a release message to this reference, you are decrementing the retain count of this object while it is still in the array. After a few times (depending on how many times the object has been retained, NSArray for example retains objects when they are added to the array) it's retain count will reach 0, and it will then become deallocated and you'll get crashes regarding unrecognised selectors or EXC_BAD_ACCESS and possibly other kinds of errors.
Related
So I have the kind of classic situation where I want to group my tableView by Month/Year. I have a member of my conference object called beginDateSearchString that I use to put different conference into buckets; my problem is in the next part where I try and fail to use a NSSortDescriptor to sort each bucket by beginDate (which is a date).
I am getting an error related to unsorted not being able to receive sort descriptor type selectors.
Here is the disgusting code:
- (NSArray *)arrayOfDateSortedEvents {
NSMutableArray *sortedArray = [[NSMutableArray alloc] init];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
//place into buckets
for (WSConference *conference in self.arrayOfEvents) {
if (![dictionary objectForKey:[conference beginDateSearchString]]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:conference,nil];
[dictionary setObject:array forKey:[conference beginDateSearchString]];
}
else {
[[dictionary objectForKey:[conference beginDateSearchString]] addObject:conference];
}
}
//sort each bucket by descriptor beginDate
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"beginDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:descriptor];
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
// now, unkey and add dictionary in order
while ([dictionary count] > 0) {
NSString *lowest = nil;
for (NSMutableArray *array in dictionary) {
if (!lowest)
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
else {
if ([(WSConference *)[array objectAtIndex:0] beginDate] < [[dictionary objectForKey:lowest] beginDate])
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
}
}
[sortedArray addObject:[dictionary objectForKey:lowest]];
[dictionary removeObjectForKey:lowest];
}
return sortedArray;
}
You want to probably filter the array in addition to sorting. See NSPredicate and the NSArray method -filteredArrayUsingPredicate: Then create an eventsByDateArray of the eventArrays created by the filter. Then in your table view delegate for creating the cells, if everything is ordered properly, the first section would represent the date of the events in the eventArray that is the first object of the eventsByDateArray and the table rows would consist of the events in the eventArray. And so on for each date.
Added
Your fast enumeration is incorrect. You enumerate through the keys of the dictionary. So in your code unsorted equals each of the keys as it enumerates. This is a GREAT lesson to everyone. It does not matter how you 'type' a variable. When Objective-C compiles it turns them all into id. So NSMutableArray *unsorted is not an NSMutableArray unless it is assigned to an NSMutableArray. If you assign unsorted to an NSString it will be an NSString. The fast enumerator for a dictionary works using the keys. So, in this case, unsorted becomes an NSString.
Instead of:
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
you should have this:
for (id key in dictionary) {
NSMutableArray *unsorted = [dictionary objectForKey:key];
[unsorted sortUsingDescriptors:sortDescriptors];
}
I eliminated all the leaks from my current app. However Instruments constantly tells me that I have a leak in the method shown below.
The leak is of type NSMutableArray and has a size of either 16 or 32 bytes. Yes, I know that's not much but it adds up. Also see it as an academic question that I need to solve to make my code leakless.
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = nil;
resMeter.sounds = nil;
resMeter.repeats = nil;
resMeter.volume = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"volumeArray"]] autorelease];
resMeter.sounds = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"soundsArray"]] autorelease];
resMeter.repeats = [[[NSMutableArray alloc] initWithArray:[dict objectForKey:#"repeatsArray"]] autorelease];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
Without looking at your Instruments output directly I can't tell exactly, but you're writing some redundant code: Try this:
+ (id) meterFromDict:(NSDictionary*)dict {
Meter* resMeter = [[Meter alloc] initWithType:[[dict objectForKey:#"MeterBase"] intValue]];
//NSLog(#"dict: %#",dict);
resMeter.volume = [dict objectForKey:#"volumeArray"];
resMeter.sounds = [dict objectForKey:#"soundsArray"];
resMeter.repeats = [dict objectForKey:#"repeatsArray"];
//NSLog(#"MeterFromDict called and resmeter.repeats count is : %i",[resMeter.repeats count]);
resMeter.bpm = [[dict objectForKey:#"BPM"] floatValue];
return [resMeter autorelease];
}
There's no point in nilling your properties before assigning new values to them.
Also, No point creating new arrays for arrays that you already have. And if you have properly declared your volume, sounds and repeats properties with copy instead of retain.
Try that and see if it works better.
I've got a problem. I'm trying to sort the items from 1 array (name: mps) into an other array (name: totalArray), but every item should be in a new array. In short: the items in mps should move into a new array, but every item should be in a new array, so the totalArray is an array of arrays. After that, totalArray should be copied back into mps.
To clarify, here is the code:
NSMutableString *oud = [[NSMutableString alloc] init];
NSMutableString *nieuw = [[NSMutableString alloc] init];
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
[tempArray removeAllObjects];
NSMutableArray *totalArray = [[NSMutableArray alloc] init];
[totalArray removeAllObjects];
tempArray = [[NSMutableArray alloc] init];
[tempArray removeAllObjects];
for (int j = 0; j < [mps count]; j++)
{
nieuw = [[mps objectAtIndex:j] valueForKey:#"ConfigurationAlias"];
if (j != 0)
{
if ([oud rangeOfString:nieuw].location == NSNotFound)
{
NSLog(#"ADDED!");
[totalArray addObjectsFromArray:tempArray];
[oud setString:nieuw];
[tempArray removeAllObjects];
[tempArray addObject:[mps objectAtIndex:j]];
}
else
{
[tempArray addObject:[mps objectAtIndex:j]];
}
}
else {
[oud setString:nieuw];
[tempArray addObject:[mps objectAtIndex:j]];
}
}
//En de array weer laden in de tabel
//NSLog(#"%#", totalArray);
NSLog(#"%i", [mps count]);
[mps removeAllObjects];
NSLog(#"REMOVED!!!");
NSLog(#"%i", [mps count]);
[mps addObjectsFromArray:totalArray];
NSLog(#"Added totalArray: %i", [totalArray count]);
NSLog(#"%i", [mps count]);
[tempArray release];
[totalArray release];
[tabelView reloadData];
}
Now, here comes my problem:
The log gives the line 2011-04-07 10:02:46.368 Fleet Manager[901:40b] ADDED! 11 times, then it posts 16 (mps count, this is correct), then the line REMOVED!, then 0 (mps count, also correct), but then the line: Added totalArray: 15. This can't be correct, it added 11 times but it counts 15?
This a problem for me, in the first place because it's plain incorrect, but the tableview gets screwed up as well because of the section-amount not being correct either (15 sections). Anybody has any idea what's going on?
EDIT: all the objects in mps are dictionaries, and all of them have a value for the key "ConfigurationAlias".
First, I would like to suggest using debugging mode instead of bunch of NSLog functions to track down what's actually happening inside your objects and variables. Try using breakpoints. Set one for [totalArray addObjectsFromArray:tempArray]; line so every time this line of code is reached the execution would stop and let you look around, check different fields' states inside totalArray and tempArray objects.
Concerning your problem... Since you are using addObjectsFromArray: that means that passed array may pass not one but several objects (thus the plural in method name). I guess one of those "11 times" added more than one element - that should explain the actual count being more than the times you've called this method. Again, please check tempArray contents.
Good luck!
If you want to end up with an array of arrays, you need to be allocating temp arrays inside the loop. It also seems like you are overdoing it with clearing out the arrays.
It appears you are trying to add the value from mps to totalArray regardless of the conditions, so it would make sense to only do that in one place (outside of the conditions).
Also, if you are not mutating the arrays (such as tempArray), you can use NSArray instead, which should be smaller and faster.
Finally, you should be able to assign the new array to mps instead of doing the expensive array copying. Not sure if that is somehow referenced as the table delegate, though.
I would try something like:
NSMutableString *oud = [[NSMutableString alloc] init];
NSMutableString *nieuw = [[NSMutableString alloc] init];
NSMutableArray *tempArray;
NSMutableArray *totalArray = [[NSMutableArray alloc] init];
for (int j = 0; j < [mps count]; j++)
{
tempArray = [[NSMutableArray alloc] init];
[totalArray addObject:tempArray];
[tempArray release];
nieuw = [[mps objectAtIndex:j] valueForKey:#"ConfigurationAlias"];
if (j != 0)
{
if ([oud rangeOfString:nieuw].location == NSNotFound)
{
NSLog(#"ADDED!");
[oud setString:nieuw];
}
}
else {
[oud setString:nieuw];
}
}
NSLog(#"%i", [mps count]);
[mps removeAllObjects];
NSLog(#"REMOVED!!!");
NSLog(#"%i", [mps count]);
[mps addObjectsFromArray:totalArray];
NSLog(#"Added totalArray: %i", [totalArray count]);
NSLog(#"%i", [mps count]);
[totalArray release];
[tabelView reloadData];
}
You should
sort the base array in a tempArray
create an empty resultArray
iterate through tempArray and for each item create an itemArray containing the item on the first position and add this array to resutArray
don't forget to release all alloc-ed temporary data
Check this out for sorting easily: Sorting arrays via Apple
I hate asking memory management questions - all the good answers
are variations on RTFM. But this one is stumping me.
I have a (relatively) complex dictionary in my model class,
where each key points to an array of arrays. I constantly add and delete
items to it, depending on state. Each "item" is an array.
- (void)addToDictionary:(NSNumber *)itemID {
// get what we need (associated array of arrays & key) from the incoming ID
NSArray *incomingArray = [self getArrayFromID:[itemID intValue]];
NSString *myKey = [incomingArray objectAtIndex:0];
NSMutableArray *myNewArray = [[NSMutableArray alloc] init];
// case 1: this key is not in the dictionary yet
if ([[myDict allKeys] containsObject:myKey] == NO) {
[myNewArray addObject:incomingArray];
[myDict setObject:myNewArray forKey:myKey];
// case 2: key already there; add new array to its array
} else {
myNewArray = [NSMutableArray arrayWithArray:[myDict objectForKey:myKey]];
[myNewArray addObject:incomingArray];
[myDict removeObjectForKey:myKey];
[myDict setObject:myNewArray forKey:myKey];
}
// why isn't this line working??
[myNewArray release];
}
My question is the last line. I allocated this array to help me
work with the dictionary, and now I don't need it any more.
But the program will crash if I release it, and work just fine
if I comment that line out. What am I missing?
TIA
In case two you don't own the returned array. So only release it in case 1. And don't create something you'll not use. The NSMutableArray pointer will get assigned to some other data in case 2, not the one you've allocated. So you can't release something you don't own.
- (void)addToDictionary:(NSNumber *)itemID {
NSArray *incomingArray = [self getArrayFromID:[itemID intValue]];
NSString *myKey = [incomingArray objectAtIndex:0];
NSMutableArray *myNewArray;
if ([[myDict allKeys] containsObject:myKey] == NO) {
// Create when you need it
myNewArray = [[NSMutableArray alloc] init];
[myNewArray addObject:incomingArray];
[myDict setObject:myNewArray forKey:myKey];
// release when you're done with it
[myNewArray release];
} else {
myNewArray = [NSMutableArray arrayWithArray:[myDict objectForKey:myKey]]; // you don't own it!
[myNewArray addObject:incomingArray];
[myDict removeObjectForKey:myKey];
[myDict setObject:myNewArray forKey:myKey];
}
// why isn't this line working??
//[myNewArray release];
// because in case 2 it's not pointing to the right memory
}
Hope it works,
ief2
I have got I have got two methods both in different classes. One is class method and other is instance method. i am calling class method from instance method. When instance method finishes it gives runtime error "EXC_BAD_ACCESS".
#import "xmlObject.h"
#import "textmeAppDelegate.h"
#implementation Class1
- (void)method1 {
textmeAppDelegate *del = (textmeAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
UIColor *color = [UIColor colorWithRed:[[bgColor objectAtIndex:3] floatValue] green:[[bgColor objectAtIndex:2] floatValue] blue:[[bgColor objectAtIndex:1] floatValue] alpha:[[bgColor objectAtIndex:0] floatValue]];
CGContextSetFillColor(context, CGColorGetComponents([color CGColor]));
CGContextFillRect(context, rect);
[bgColor release];
}
#end
#implementation xmlObject
+ (NSArray *) fetchImmediateChildrenValues:(NSMutableDictionary *) node {
NSMutableDictionary *tmp = [[node objectForKey:#"children"] retain];
NSArray *keys = [[NSArray alloc] initWithArray:[tmp allKeys]];
keys = [keys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSMutableArray *pushArr = [[[NSMutableArray alloc] init] autorelease];
NSString *val = [[NSString alloc] init];
for(NSString *str in keys) {
val = (NSString *)[[tmp objectForKey:str] objectForKey:#"innertext"];
[pushArr addObject:val];
}
[val release];
[keys release];
return [NSArray arrayWithArray:pushArr];
}
#end
What is wrong with the code? Also app is crashing for this line of code
application is crashing if i include this line
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
If I remove it application runs smoothly.
I have several comments on your code. One of them is the immediate cause of your crash, but you need to fix at least one other issue too. The short answer is that you over release val and keys.
NSArray *bgColor = [[NSArray alloc] initWithArray:[xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]]];
You don't need to create a new array here, you can simply write the following:
NSArray *bgColor = [xmlObject fetchImmediateChildrenValues:[del.navigationbarStyle objectForKey:#"backgroundcolor"]];
if you do, you don't need the [bgColor release] further down.
NSArray *keys = [[NSArray alloc] initWithArray:[tmp allKeys]];
keys = [keys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
These two lines leak the first NSArray, you alloc it but you overwrite it straight away with the sorted version. In fact, you can simply write:
keys = [[tmp allKeys] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
Note that you do not own keys so you can get rid of the [keys release] line further down.
NSString *val = [[NSString alloc] init];
for(NSString *str in keys) {
val = (NSString *)[[tmp objectForKey:str] objectForKey:#"innertext"];
[pushArr addObject:val];
}
[val release];
This is the source of your immediate problem. You first alloc a new string. Then you immediately overwrite it on each iteration of your loop. So the allocated NSString leaks. You do not own the val returned by [[tmp objectForKey:str] objectForKey:#"innertext"]; on each iteration, so the release ov val after the loop should not be there.
On a side note, objectForKey: returns an id - the cast to NSString* is redundant. Most people leave it out.
[keys release];
Going back to the bit above where I told you that you were leaking your alloc'd keys? Well the new version of keys you overwrote it with you don't own. Therefore you must not release keys here.
return [NSArray arrayWithArray:pushArr];
This is fine. My preference would be for:
return [[pushArray copy] autorelease];
but it is just a matter of style. You could also just return pushArray, but pushArray is mutable and the caller may rely on the return value being immutable.
Test your code with NSZombieEnabled set... It should give you enough informations to fix your problem.