When should I release my array? - iphone

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.

Related

NSArray Empty After Calling reloadData

I have a class that gets data from a repository and stuffs it into an NSArray:
EpisodeRepository Class
-(NSMutableArray *)getEpisodes {
NSMutableArray *episodes = [NSMutableArray array];
NSData *data = [self getDataFromAction:kGetEpisodesAction];
NSError *error = nil;
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
for (NSDictionary *d in json) {
Episode *episode = [self buildEpisodeFromJSONDictionary:d];
[episodes addObject:episode];
}
return episodes;
}
In my view controller, I keep a reference to the repo and the array of episodes, like so:
#interface VideoController : UITableViewController
#property (nonatomic, strong) NSArray *episodes;
#property (nonatomic, strong) EpisodeRepository *episodeRepo;
In the implementation, what I want to happen is that every the view appears, refetch the data from the web services(in the EpisodeRepository) and reload the table view with the updated episodes. This works great the first time the tableview is populated. The problem is that when I call, cellForRowAtIndexPath, the episodes array is completely blank. It's not null, but there is no data whatsoever in it. The weird thing is that all the other tableView delegates realize that there is data in the array and act accordingly. What is special about cellForRowAtIndexPath and why would it possibly delete my entries in the array?
Here is the controller implementation:
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
episodeRepo = [[EpisodeRepository alloc] init];
}
return self;
}
-(void)viewWillAppear:(BOOL)animated
{
[self loadContent];
[self.tableView reloadData];
[super viewWillAppear:animated];
}
-(void) loadContent {
self.episodes = [self.episodeRepo getEpisodes];
self.seasons = [self getSeasons:self.episodes];
[self setUILoaded];
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//episodes is an empty array here???
}
I found the solution! It turns out I had code in my App Delegate under the willSelectViewController for the tab bar that was allocating the array outside of the view controller. Lesson learned: don't do this!

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.

Strange behaviour when setting property in iPhone app

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
PushOnStackViewController *vc = [[PushOnStackViewController alloc] init];
vc.key = [self.keys objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:vc animated:YES];
}
and
in the init method of the PushOnStackViewController class I have
- (id)init {
self.navigationItem.title = key;
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"texts" ofType:#"plist"]];
self.keys = [dict objectForKey:key];
[dict release];
NSLog(#"%#", self.key);
NSLog(#"%i", [self.keys count]);
return self;
}
But why can't I access the self.key? It returns null, even though it has been set(it is a string).
When I access it in viewDidLoad it returns the correct value...anything I haven't read, or am I doing anything wrong?
Thanks in advance.
You can't access self.key inside the -init function because at that point it hasn't been set yet. you are setting it afterwards:
PushOnStackViewController *vc = [[PushOnStackViewController alloc] init]; // init runs here.
vc.key = [self.keys objectAtIndex:[indexPath row]]; // but you don't set the key property until here.
You might try adding a "key" parameter to the init function, like so:
-(id)initWithKey:(NSString*)key {
self = [super init];
if (self) {
self.key = key;
...etc...
}
return self;
}
Your init method is called before you set the property. Get rid of that init method and move your code into viewDidLoad to ensure that it's called after you've done all the property setup.
Don't create new init method for a UIViewController unless you know what you're doing. It's much easier to create a property (like you've done) and access that property inside the viewDidLoad 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.

How do you get and set a class property across multiple functions in Objective-C?

Update: Unfortunately the help offered below did not solve my problem of sharing a class property across functions. Can anyone else suggest a possible problem? Here's the latest code:
Header .h:
#interface FirstViewController:UIViewController <UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate> {
NSDictionary *sectorDictionary;
NSInteger sectorCount;
}
#property (nonatomic, retain) NSDictionary *sectorDictionary;
- (id)initWithData:(NSMutableDictionary*)inData;
Implementation .m:
#synthesize sectorDictionary;
- (id) testFunction:(NSDictionary*)dictionary {
NSLog(#"Count #1: %d", [dictionary count]);
return nil;
}
- (id)initWithData:(NSMutableDictionary *)inData {
self = [self init];
if (self) {
[self testFunction:inData];
// set the retained property
self.sectorDictionary = inData;
}
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count #2: %d", [self.sectorDictionaryCopy count]);
return [self.sectorDictionaryCopy count];
}
Console output:
2010-05-05 20:11:13.126 JSONApp[17378:207] Count #1: 9
2010-05-05 20:11:13.130 JSONApp[17378:207] Count #2: 0
Following up on this question about sharing an object between classes, I now need to figure out how to share that object across various functions in a class.
First, the setup: In my App Delegate I load menu information from JSON into a NSMutableDictionary and message that through to a view controller using a function called initWithData. I need to use this dictionary to populate a new Table View, which has methods like numberOfRowsInSection and cellForRowAtIndexPath.
I'd like to use the dictionary count to return numberOfRowsInSection and info in the dictionary to populate each cell. Unfortunately, my code never gets beyond the init stage and the dictionary is empty so numberOfRowsInSection always returns zero.
I thought I could create a class property, synthesize it and then set it. But it doesn't seem to want to retain the property's value. What am I doing wrong here?
In the header .h:
#interface FirstViewController:UIViewController <UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate> {
NSMutableDictionary *sectorDictionary;
NSInteger sectorCount;
}
#property (nonatomic, retain) NSMutableDictionary *sectorDictionary;
- (id)initWithData:(NSMutableDictionary*)data;
#end
in the implementation .m:
#synthesize sectorDictionary;
- (id) testFunction:(NSMutableDictionary*)dictionary {
NSLog(#"Count #1: %d", [dictionary count]);
return nil;
}
- (id)initWithData:(NSMutableDictionary *)data {
if (!(self=[super init])) {
return nil;
}
[self testFunction:data];
// this is where I'd like to set a retained property
self.sectorDictionary = data;
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Count #2: %d", [self.sectorDictionary count]);
return [self.sectorDictionary count];
}
Output from NSLog:
2010-05-04 23:00:06.255 JSONApp[15890:207] Count #1: 9
2010-05-04 23:00:06.259 JSONApp[15890:207] Count #2: 0
Any init function should return self if it completes successfully. Change the return nil to return self at the end of initWithData.
-(id) initWithData:(NSMutableDictionary *)inData {
self = [self init]; // best practice use super for method with same name
if ( self ) {
self.sectorDictionary = inData;
}
return self;
}
The sectorDictionary member should not be mutable. If it changes, you need to call reloadData on the table. Your setter function should make an immutable copy.
-(void) setSectorDictionary:(NSDictionary *)inData {
NSDictionary *old = sectorDictionary;
sectorDicitonary = inData ? [[NSDictionary alloc] intWithDictionary:inData] : nil;
[self.table reloadData];
[old release];
}
I believe your problem lies within the line self.sectorDictionary = data.
You need to firstly allocate some memory for the dictionary you are creating, so a line like
self.sectorDictionary = [[NSMutableDictionary alloc] init]; is going to be necessary.
After you have initiated the new dictionary you need to populate it with the contents of the dictionary that was passed in.
So...
Try the line:
self.sectorDictionary = [[NSMutableDictionary alloc] initWithDictionary:data];
instead of:
self.sectorDictionary = data;
instead of
self.sectorDictionary = data;
try this way...
self.sectorDictionary = [[NSMutableDictionary alloc] init];
self.sectorDictionary = (NSMutableDictionary *) data;