remove rows of UITableView causes memory leak - iphone

I hope to delete all rows of an UITablewView
The UITableView is "atableview", its data source is "fileArray".
NSMutableArray *fileArray;
fileArray is NSMutableArray for object MYFileObj
#import <UIKit/UIKit.h>
NSMutableArray *fileArray;
#interface MYFileObj : NSObject {
NSString *fileName;
}
-(void) setFileName:(NSString *)s ;
-(NSString *) FileName ;
#end
I load fileArray at first, then call [atableview reloadData];
after do something, I hope to reload fileArray and redraw atableview, so I call
-(void) removeACell:(NSInteger)row;
{
NSUInteger _lastSection = 0;//[self numberOfSectionsInTableView:atableview];
NSUInteger _lastRow =row;// [atableview numberOfRowsInSection:_lastSection] - 1;
NSUInteger _path[2] = {_lastSection, _lastRow};
NSIndexPath *_indexPath = [[NSIndexPath alloc] initWithIndexes:_path length:2];
NSArray *_indexPaths = [[NSArray alloc] initWithObjects:_indexPath, nil];
[_indexPath release];
[atableview deleteRowsAtIndexPaths:_indexPaths withRowAnimation: UITableViewRowAnimationNone];
[_indexPaths release];
}
-(void) reloadList;
{
if([fileArray count]>0) //----the begining of the codes cause memory leak
{ //I hope to remove all rows and reload fileArray
NSInteger n=fileArray.count;
[atableview beginUpdates];
for(int i=n-1;i>=0;i--)
{
[fileArray removeObjectAtIndex:i];
[self removeACell:i];
}
[fileArray release];
[atableview endUpdates];
} //----the end of the codes cause memory leak
//load fileArray again
[atableview reloadData];
}
But I found this cause memory leak.
Welcome any comment.
Thanks
interdev

While there are times where you want to delete a row or rows manually, your code doesn't seem to be one of those cases because you're then turning around and calling reloadData on the tableview.
when things change in your table, make the changes to the backing data source first -- fileArray -- and then call reloadData and all will be fine.
Next, you don't have to remove objects from the array in a loop if you're emptying it completely: just use [fileArray removeAllObjects]; (actually, since you're releasing fileArray after your loop, you could reduce all that logic to [fileArray release]; and it will send a release to each of it's objects.
Not sure where your mem leak is -- there's plenty of code we can't see, but cleaning up the logic as described will help you out.

Related

Array is suddenly empty

For my UITableView I use an array as a datasource. Everything works fine so far. However, I have the weird issue, that when I use the search field and enter a few characters, which I afterwards delete again, the underlying array is suddenly empty. Here the code snippets, which might be relevant to understand my issue:
Declaration in my .h
#interface dictionaryViewController : UIViewController <UITableViewDelegate>{
...
...
NSMutableArray *cardArray;
}
...
#property (retain) NSMutableArray *cardArray;
...
Usage in my .m code:
#synthesize cardArray;
...
- (void)viewDidLoad {
[super viewDidLoad];
self.cardArray = [[NSMutableArray alloc] initWithObjects:nil];
...
}
I populate the array with data from my SQL DB:
[self.cardArray addObject:[NSString stringWithFormat:#"%# - %#", aQuestion, anAnswer]];
And within the code read the content of the array like in the cellForRow method:
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
thisCardIndex = [self.cardArray indexOfObject:cellValue];
...
}
At the end I release it, like this (I actually had some other issues with the release command, why I used the removeObjects instead):
[self.cardArray removeAllObjects];
self.cardArray=nil;
In the log I do not see an error. The debugger shows, however, that the code crashes with a SIGABRT and when setting breakpoints I see, that the cause is the empty cardArray.
Thanks for the support in advance.
ok, finally found the culprit, it was the [myArray release] (see commented line below). I have no clue, WHY though. This is a local array, which I define locally and also should be able to release immediatly again. And the interesting part is, that this code works as long as the search is narrowing. It only crashes, when the search field is empty again. VERY confusing, but maybe someone has an explanation?? Anyway, finally got it and it ru as expected.
- (void) searchTableView {
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] initWithObjects:nil];
for (NSDictionary *dictionary in self.listOfItems) {
NSArray *myArray = [dictionary objectForKey:#"Cards"];
[searchArray addObjectsFromArray:myArray];
// [myArray release];
}
// Counter is needed to get the index of the primary key to dislpay the card in editViewController
int aCounter=0;
for (NSString *sTemp in searchArray) {
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0) {
[self.aCopyListOfItems addObject:sTemp];
NSInteger myPrimaryKey;
myPrimaryKey = [[self.cardIDArray objectAtIndex:aCounter] integerValue];
[self.aCopyOfCardIDArray addObject:[NSNumber numberWithUnsignedInteger: myPrimaryKey]];
}
}
[searchArray removeAllObjects];
searchArray = nil;
}

How to solve a error when I call the method [self somemethod]

I have this code:
// .m
- (void)viewDidLoad {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self addToArray];
}
- (void)addToArray {
NSString *stringA;
[stringA isEqualToString:#"door"];
NSString *stringB;
[stringB isEqualToString:textField.text];
[array addObject:stringA];
if ([stringA isEqual:stringB]) {
[stringA isEqual:nil];
[tableView reloadData];
} else {
[array addObject:stringB];
[tableView reloadData];
}
}
When I call the method addToArray it keeps returning me an error called Thread 1: Program recived signal "EXC_BAD_ACCESS", and the debugger output says : Single stepping until exit from function objc_msgSend, which has no line number information. at the line [self addToArray]. Any idea of how to solve it? I have wasted to much time with it, please help me!
As was said by others, array should be an instance variable or property of the class, declared in the .h file:
#property (strong) NSMutableArray *array;
Or, without ARC:
#property (retain) NSMutableArray *array;
Now you #synthesize array; in your implementation file and can access it from anywhere. Then you can do:
- (void) viewDidLoad
{
self.array = [[NSMutableArray alloc] init];
[self addToArray];
}
You seem to assume that isEqualToString does an assignment. It doesn't, it checks strings for (textual) equality. Try this:
- (void) addToArray
{
NSString *stringA = #"door";
NSString *stringB = textField.text;
[array addObject: stringA];
if (![stringA isEqualToString: stringB])
[array addObject: stringB];
[tableView reloadData];
}
These two variables are uninitialized and will cause you big problems:
NSString *stringA;
[stringA isEqualToString:#"door"];
NSString *stringB;
[stringB isEqualToString:textField.text];
You have not assigned anything to either stringA or stringB. Besides the result of your call to isEqualToString is never used.
Two things I can notice in your code:
1) Make array a class variable, so you can access it from your -[addToArray] method. Better do this in your .h file, for example:
#interface MyViewController : UIViewController {
#private
// ...skipped...
NSMutableArray * array;
// ...rest of class skipped...
}
#end
Then, in your .m file the method should look like this:
// .m
- (void)viewDidLoad {
array = [[NSMutableArray alloc] init];
[self addToArray];
}
And don't forget to release the array:
- (void)dealloc {
[array release];
[super dealloc];
}
2) Do not mess up -[NSString isEqualToString:] method with simple assigment to a variable. So in your -[addToArray] method, for example, replace this:
NSString *stringA;
[stringA isEqualToString:#"door"];
with this:
NSString *stringA = #"door";
And this:
NSString *stringB;
[stringB isEqualToString:textField.text];
with this:
NSString *stringB = textField.text;
3) Check the logic of -[addToArray] method - it is not very clear what are you going achieve.

PickerView crashes out EXC_BAD_ACCESS when moved

I have a pickerView, it appears in the Simulator but as soon as I try and scroll it, it crashes EXC_BAD_ACCESS in the main.m file.
I know it is to do with the arrays that I load in from a pList because when I try this with arrays that are initialised in the program it works (see commented out sections), is this something to do with 'nil' at the end of the array? If so how can I see this and how can I add it.
I appreciate any help on this please I'm pulling what's left of my hair out, and I'm fairly new to this...
// Method to define the numberOfComponent in a picker view.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 2;
}
// Method to define the numberOfRows in a component using the array.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent :(NSInteger)component
{
if (component==0)
{
return [maximumSpeed count];
}
else
{
return[warningTime count];
}
}
//PickerViewController.m
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSString *stringToReturn = nil;
switch (component)
{
case 0:
stringToReturn = [maximumSpeed objectAtIndex:row];
break;
case 1:
stringToReturn = [warningTime objectAtIndex:row];
break;
}
return stringToReturn;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
// finding the path and loading the pList as a dictionary into 'data'
NSString *pListFile = [[NSBundle mainBundle] pathForResource:#"PropertyList" ofType:#"plist"];
data =[NSDictionary dictionaryWithContentsOfFile:pListFile];
// get and set up the 2 arrays
maximumSpeed = [data valueForKey:#"MaximumSpeed"];
warningTime = [data valueForKey:#"WarningTime"];
//maximumSpeed =[[NSArray alloc] initWithObjects:#"Running",#"Crying",#"Boring",#"Working",nil];
//warningTime = [[NSArray alloc] initWithObjects: #"Happy", #"Sad" , #"Good", #"joyce",nil];
NSLog(#"%#", maximumSpeed);
NSLog(#"%#", warningTime);
}
(void)viewDidUnload
{
[self setTxt1:nil];
[pickerView release];
pickerView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[txt1 release];
[pickerView release];
[warningTime release];
[maximumSpeed release];
[super dealloc];
}
You do not own the object(s) returned by valueForKey, in this case, your arrays. These objects are getting released and you end up with a dangling pointer. Hence, the crash. The problem is here:
maximumSpeed = [data valueForKey:#"MaximumSpeed"];
warningTime = [data valueForKey:#"WarningTime"];
You need to retain them.
maximumSpeed = [[data valueForKey:#"MaximumSpeed"] retain];
warningTime = [[data valueForKey:#"WarningTime"] retain];
The following works because you own the objects you create.
//maximumSpeed =[[NSArray alloc] initWithObjects:#"Running",#"Crying",#"Boring",#"Working",nil];
//warningTime = [[NSArray alloc] initWithObjects: #"Happy", #"Sad" , #"Good", #"joyce",nil];
This also applies to dictionaryWithContentsOfFile:
data =[NSDictionary dictionaryWithContentsOfFile:pListFile];
You can retain (like before) or switch to the alloc-init version:
data = [[NSDictionary dictionaryWithContentsOfFile:pListFile] retain];
// or
data = [[NSDictionary alloc] initWithContentsOfFile:pListFile];
You do not own the objects returned by methods unless the method name begins with "alloc", "new", "copy" or "mutableCopy".
As it has been said already, you could copy the objects (arrays) maximumSpeed and warningTime or, alternatively, you retain and release them.
Talking about releasing ... You are releasing pickerView twice. First in viewDidUnload and second in dealloc. It may actually work without a crash because you assign nil to it after the first release and therefore the second release should be processed without a crash anyway.
However, that indicates that you should be more careful about your memory management, retain-release-pairs in particular.
Why don't you track the zombies in the instrumemts application? You start it in xcode4 with cmd-i.
In most cases that gives some useful hint on where to have a detailed look on missing retains (or copies).

When should I release my array?

I am parsing some JSON from the internet and then adding them to an array which is the datasource for my UITableView. I am not sure when I should be releasing my array?
.h: items
#property(nonatomic,retain)NSMutableArray* items;
.m: connectionDidFinishLoading
// fetch succeeded
NSString* json_string = [[NSString alloc] initWithData:retrievedData encoding:NSUTF8StringEncoding];
//Check ST status
int status = [[[[json_string objectFromJSONString] valueForKey:#"response"] valueForKey:#"status"]intValue];
//NSLog(#"Status: %d", status);
items = [[NSMutableArray alloc] init];
NSDictionary* messages = [[NSDictionary alloc] init];
switch (status) {
case 200:
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
for (NSDictionary *message in messages)
{
[items addObject:message];
}
[self.tableView reloadData];
break;
default:
break;
}
One, you might want to declare items as an instance of NSMutableArray if you intend to call addObject: on it.
Two, declare it as a property so that if you end up getting it multiple times the older value will be released when you do.
self.items = [NSMutableArray array];
And the correct point of releasing it would be dealloc.
Probably you don't want to release it immediately if you:
use didSelectRowAtIndexPath: method for detail views and pass this data to them
define custom UITableViewCell styles in cellForRowAtIndexPath: method
use this data elsewhere
Best practice is declare an instance variable and synthesize it in .m, use in appropriate operations and release in dealloc method.
One possible release point that you could use is where you refresh your data that shown on table.
Example:
I get dictionaries in an array from an API in my app and use something like that.
MyTableViewController.h
#interface MyTableViewController {
NSMutableArray *items;
}
#property (nonatomic, retain) NSMutableArray *items;
#end
MyTableViewController.m
#implementation MyTableViewController
#synthesize items;
- (void)dealloc
{
[items release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"FilesCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
cell.textLabel.text = [[items objectAtIndex:indexPath.row] valueForKey:#"name"];
cell.imageView.image = [UIImage imageNamed:[[NSString alloc] initWithFormat:#"filetype_%#.png", [[items objectAtIndex:indexPath.row] valueForKey:#"type"]]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyDetailViewController *detailViewController = [[MyDetailViewController alloc] initWithNibName:#"MyDetailViewController" bundle:[NSBundle mainBundle]];
detailViewController.item = [items objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
detailViewController = nil;
}
}
- (void)getItems
{
[items release];
items = [[NSMutableArray alloc] init];
//Do some requests here
for (NSDictionary *dict in results)
{
[items insertObject:dict atIndex:0];
}
[self.tableView reloadData];
[self stopLoading];
}
#end
Releasing at wrong places some time lead to memory leaks, before allocation itself u can have a condition like if() { [...release]}.Not tested but this kind of release avoid leaks.
The most common is to have the items variable as an attribute of your class, once you will probably need it to use in your tableView:cellForRowAtIndexPath: method.
So, having it as an attribute variable you can release it on the dealloc method.
It's clear that your array item will be used by UITableView to show data.
First declare it as instance variable in your .h class.
.h class
#interface MyClass
{
MSMutableArray* items;
}
#property(nonatomic,retain) MSMutableArray* items;
#end
In your .m class.
#synthesis iMyArray;
And you code for filling the array should be
NSMutabelArray* itemsTemp = [[NSMutabelArray alloc] initWithCapacity:1];
messages = [[[json_string objectFromJSONString] valueForKey:#"messages"] valueForKey:#"message"];
[json_string release];
for (NSDictionary *message in messages) {
NSLog(#"%#",[message valueForKey:#"body"]);
[itemsTemp addObject:message];
}
self.items= itemsTemp;
[itemsTemp release];
itemsTemp = nil;
[self.tableView reloadData];
Now in dealloc release your array instance.
-(void) dealloc
{
if(items )
{
[items release];
items = nil ;
}
[super dealloc];
}
Proper way is make it property in .h class, since you have declared it as property: remember one thing always alloc a property by using self.
your statement items=[[NSMutableArray alloc] init];
is wrong.(use self) also since your property is retain type the using alloc on it increase retain count.that gives you a leak.
so use in this way in viewDidLoad
NSMutableArray *tempArray=[[NSMutableArray alloc] init];
self.items=tempArray;
[tempArray release];
then release your items array in dealloc and set it nil in viewDidUnload
- (void)viewDidUnload {
[super viewDidUnload];
self.items=nil;
}
- (void)dealloc {
[self.items release];
[super dealloc];
}
Hope now you can understand how you should use this.
According to Apple's documentation of UITableView reloadData method:
"[...] For efficiency, the table view redisplays only those rows that are visible"
That means yo should not release the items array as long as the table is being used, i.e. you have to declare the array as a property.
First because if you scroll the view, you will still need the items information to display the rows below or above.
And second, because by being a property you ensure that a previous value is going to be released if you happen to assign a new value to items.
Finally, the common place to release a property is in the dealloc method and depending on your implementation in viewDidUnload method.

Memory leaks in UITableView with NSMutableArray - How to stop them?

I'm pretty new to objective-c development and I'm to the point I'm beginning to test my application for leaks and patching up anything else I may have done wrong originally. I followed the examples from a book I bought and expanded on those ideas. The Leaks instrument is telling me in my tableView cellForRowAtIndexPath method I have a leak and I'm not sure on how to fix it.
Here is the related .h contents:
#interface NewsListViewController : UITableViewController<UIActionSheetDelegate> {
NSMutableArray *newsList, *account, *playerList;}
And here is the related .m contents:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)ip {
static NSString *CellIdentifier = #"NewsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
[cell autorelease];
}
NSManagedObject *uNews = [newsList objectAtIndex:[ip row]];
NSManagedObjectContext *playerDBContext = [[AppController sharedAppController] managedObjectContext];
NSFetchRequest *playerDBRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *playerDBEntity = [NSEntityDescription entityForName:#"Players"
inManagedObjectContext:playerDBContext];
[playerDBRequest setEntity:playerDBEntity];
NSPredicate *playerDBPredicate = [NSPredicate predicateWithFormat:#"playerID=%#", [uNews valueForKey:#"playerID"]];
[playerDBRequest setPredicate:playerDBPredicate];
NSError *playerDBError;
NSArray *playerDBList = [playerDBContext executeFetchRequest:playerDBRequest error:&playerDBError];
[playerDBRequest release];
playerList = [playerDBList mutableCopy];
NSString *playerInformation;
if (![playerDBList count] == 0) {
NSManagedObject *playerInfo = [playerList objectAtIndex:0];
playerInformation = [NSString stringWithFormat:#"%#, %# (%#-%#)", [playerInfo valueForKey:#"playerLastName"],
[playerInfo valueForKey:#"playerFirstName"],
[playerInfo valueForKey:#"team"],
[playerInfo valueForKey:#"position"]];
} else {
//NSInteger playerID = (NSInteger *)[uNews valueForKey:#"playerID"];
[self addPlayer:(NSInteger *)[uNews valueForKey:#"playerID"]];
NSLog(#"%#", [uNews valueForKey:#"playerID"]);
playerInformation = [uNews valueForKey:#"playerInfo"];
}
cell.textLabel.text = playerInformation;
cell.detailTextLabel.text = [uNews valueForKey:#"news"];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;}
It's throwing the error on the playerList = [playerDBList mutableCopy]; line - Help with how to fix and an explanation would be greatly appreciated. It's probably from reallocating without releasing but when I've tried using [playerList release]; at the end of the cellForRowAtIndexPath my app crashes.
Properties would make this 'just work'.
.h:
...
#property (nonatomic, retain) NSMutableArray *playerList;
...
.m:
#implementation MyClass
#synthesize playerList;
... then in your cellForIndexPath method ...
self.playerList = [[playerDBList mutableCopy] autorelease];
...
- (void)dealloc {
[playerList release];
[super dealloc];
}
A property declared 'retain' will automatically handle memory management when the property is assigned, releasing the old value if it exists before retaining the new one.
The release you tried crashed because the first time through playerlist hasn't ever been assigned and you release a nil. But the second time through it has something and you leak it. Whenever I reuse a retaining pointer like that, I do
if( playerList )
[playerList release];
playerList = [playerDBList mutableCopy];
just to be safe.