Zombie messaged by [array count] - objective-c

I have an ivar mutable array which i setup in viewDidLoad as follows:
names = [NSMutableArray arrayWithCapacity:30];
[names addObject:#"Joe"];
[names addObject:#"Dom"];
[names addObject:#"Bob"];
Then in a later method, on tap of a button, i do the following, but the array appears to be overreleasing... with Zombie messaged:
int r = arc4random() % [names count];
NSLog(#"%d", r);
How do i fix this?
Thanks.

+arrayWithCapacity: will return an auto-released object, i.e. in the "later method" this object is likely already deallocated. You need to retain this object to make it available "later".
names = [[NSMutableArray arrayWithCapacity:30] retain];
(alternatively,
names = [[NSMutableArray alloc] initWithCapacity:30];
)
Don't forget to release it in -dealloc.
-(void)dealloc {
[names release];
...
[super dealloc];
}

Related

Objective-C Array of Objects

Although experienced with OOP, I am an absolute newbie with Objective-C. I have the following code:
// header files have been imported before this statement...
CCSprite *treeObstacle;
NSMutableArray *treeObstacles;
#implementation HelloWorldLayer {
}
-(id) init
{
// create and initialize our seeker sprite, and add it to this layer
treeObstacles = [NSMutableArray arrayWithObjects: nil];
for (int i=0; i<5; i++) {
treeObstacle = [CCSprite spriteWithFile: #"Icon.png"];
treeObstacle.position = ccp( 450-i*20, 100+i*20 );
[self addChild:treeObstacle];
[treeObstacles addObject: treeObstacle];
}
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
return self;
}
- (void) mymethod:(int)i {
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
}
#end
The first NSLog() statement returns "Number of elements in array = 5". The problem is that (although treeObstacles is a file-scope variable) when calling the method "mymethod", I'll get an EXC_BAD_ACCESS exception.
Can anybody please help me?
Thanks a lot
Christian
you created treeObstacles by
treeObstacles = [NSMutableArray arrayWithObjects: nil];
which will return an autoreleased object, and you didn't retain it so it will be released soon
you have to retain it by calling retain on it
[treeObstacles retain];
of simple create it by
treeObstacles = [[NSMutableArray alloc] init];
and you need to remember to release it when done like
- (void)dealloc {
[treeObstacles release];
[super dealloc];
}
you need to read more about management in Objective-C
https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/MemoryManagement.html
or use ARC so no need to worry retain/release anymore
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
another problem, you need to call [super init] in your init method
- (id)init {
self = [super init];
if (self) {
// your initialize code
}
}
otherwise your object won't initialize properly

My NSMutableArray loses its objects outside of function scope even after alloc / init

I'm totally stumped on this one. I have an NSMutableArray which is declared in my header and set as a property, synthesized etc. I then call a function that allocates and initializes the array, and I add custom objects to it. I do a for each loop after the objects are added to ensure that they are actually contained within the array and they are. Once the program goes outside of this function scope, though, suddenly the array is empty.
header file:
#interface ScheduleViewController : UITableViewController {
NSString *login_id;
NSMutableArray *events;
}
- (id)initWithID:(NSString*)l_id;
- (void)grabURLInBackground; // ASIHTTP example method
#property (nonatomic, retain) NSString *login_id;
#property (nonatomic, retain) NSMutableArray *events;
#end
implementation:
#synthesize events;
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *response = [request responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *eventDics = [parser objectWithString:response error:nil]; // an array of dictionaries of events
NSDateFormatter *dateForm = [[NSDateFormatter alloc] init];
// Allocate empty event object and initialize the mutable array
Event* event = [[Event alloc] init];
self.events = [[NSMutableArray alloc] initWithCapacity:[eventDics count]];
// loop through the array of dictionaries
for (int i = 0; i < [eventDics count]; i++)
{
NSDictionary *dict = [eventDics objectAtIndex:i];
for(NSString *key in dict) {
// for the sake of readability i wont include the code
// but the event is populated here
}
[self.events addObject:event];
[event release];
}
NSLog(#"Array Count: %i", [self.events count]);
for (Event *e in events) {
NSLog(#"eventid: %i, type: %#, price: %f, name: %#", e.event_id, e.type, e.price, e.name);
}
[parser release];
[dateForm release];
}
So the above code works fine and prints out the variables from the Event objects that are stored in the events mutable array.
What I want to do is use the events array in another function now, and when I try to, the count is 0 and also no objects are stored in the array when I look at it.
In viewDidUnload I set self.events = nil; and in dealloc I do [self.events release]
You are doing your alloc/init for the Event *event object outside of your for loop. This means you are adding the same object every time you add it to the array. You should move this line:
Event* event = [[Event alloc] init];
To the inside of your
for (int i=0 ... loop.
Not sure that would explain the symptoms you are seeing, but it could, since the following statement:
[event release]
is also releasing that one allocated object once for every time through the loop - so you are releasing the object multiple times. If you move the Event alloc to the inside of the loop then the release will be ok. (adding the object to the array will retain it so its ok to release it, but you need to allocate a new Event each time through the loop).
Basically your code should look like this: (note I've also added an autorelease to your array alloc).
// Allocate empty event object and initialize the mutable array
self.events = [[[NSMutableArray alloc] initWithCapacity:[eventDics count]] autorelease]; // assigning to the retain property will retain it, so autorelease it or it will be retained twice. Could also have used the arrayWithCapacity convenience method here instead and then wouldn't need to autorelease.
// loop through the array of dictionaries
for (int i = 0; i < [eventDics count]; i++)
{
Event* event = [[Event alloc] init]; // Allocate a new Event each time through the loop so you are adding a unique object to the array each time.
NSDictionary *dict = [eventDics objectAtIndex:i];
for(NSString *key in dict) {
// for the sake of readability i wont include the code
// but the event is populated here
}
[self.events addObject:event];
[event release];
}
I see many problems with this code. These include the fact that you are releasing objects at inappropriate times, and that you are getting confused about the scope of different objects. It seems to me that one of the biggest problems that you are having is re-allocating your events array every time the requestFinished: method is called. In your init method, you should do something like this:
- (id)init {
if ((self = [super init])) {
// Since it is a retain property, we should autorelease it when
// assigning to it, thus preventing an extra retain.
self.events = [[[NSMutableArray alloc] initWithCapacity:[eventDics count]] autorelease];
}
}
With that being said, here is how I would rewrite your requestFinished: method, as well as your dealloc method:
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *response = [request responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *eventDics = [parser objectWithString:response error:nil]; // an array of dictionaries of events
NSDateFormatter *dateForm = [[NSDateFormatter alloc] init];
// Clear the already allocated events array
[self.events removeAllObjects];
for (int i = 0; i < [eventDics count]; i++) {
// note how I assign event in here
Event *event = [[Event alloc] init];
NSDictionary *dict = [eventDics objectAtIndex:i];
for (NSString *key in dict) {
// Do whatever it is you do here
}
[self.events addObject:event];
[event release];
}
NSLog(#"Array Count: %i", [self.events count]);
for (Event *e in events) {
NSLog(#"eventid: %i, type: %#, price: %f, name: %#", e.event_id, e.type, e.price, e.name);
}
[parser release];
[dateForm release];
}
Finally, you can simply set the events property to nil in the dealloc method:
- (void)dealloc {
self.events = nil;
[super dealloc];
}
The only reason that I can think of for the array being empty is that a) it's contents are being deallocated, or b) it itself is being deallocated and set to nil. The pieces of your code that I fixed could possibly cause both of these. Try the changes that I have made, and see if they make a difference.
So I figured out the problem and it's due to an error on my part. After stepping through the function calls more closely, it turns out that the table view delegate method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
was being called before populating the array with requestHasFinished. I was calling requestHasFinished indirectly through viewDidLoad but I think that the table view delegate method was being called when the view controller is initialized. Init was being called before viewDidLoad because the view controller is actually handled within a tab view controller which initializes all of the view controllers for each tab at the time when itself is initialized. That's another matter to get into.. I'm not sure if I like everything being initialized and setup before the views are even displayed but.. something to research.
Anyways thanks again for the help.

I cant find this memory leak. I thought I was releasing everything properly

I cannot find this memory leak. I thought I had been releasing things properly. Here is the block of code in question.
- (void) createProvince:(NSString *) provinceName {
// if province does not exist create it
if ([self hasProvince: provinceName] == NO) {
// get the province object
NSPredicate *predicate;
predicate = [NSPredicate predicateWithFormat:#"Name == %#", provinceName];
NSMutableArray *provArray = [[NSMutableArray alloc] init];
[provArray setArray: [CoreDataHelper searchObjectsInContext:#"Province" :predicate :#"Name" :YES :[self managedObjectContext]]];
NSIndexPath *indexPath;
indexPath = [NSIndexPath indexPathForRow:0 inSection: 0];
[[self provinces] addObject: [provArray objectAtIndex: [indexPath row]]];
[provArray release];
// create a cities array to hold its selected cities
NSMutableArray *array = [[NSMutableArray alloc] init];
[[self cities] addObject: array];
[array release];
}
}
The leaks are here:
[[self provinces] addObject: [provArray objectAtIndex: [indexPath row]]];
NSMutableArray *array = [[NSMutableArray alloc] init];
[[self cities] addObject: array];
I am creating the local variables, assigning them to my instance variables through the proper setters, and then releasing the local variables. I am not sure what is going on.
Do you have a dealloc method that is properly releasing everything?
Note that leaks is showing you where something was allocated. It doesn't show you where it was actually leaked; what retain wasn't explicitly balanced.
Let's look at this:
NSMutableArray *array = [[NSMutableArray alloc] init];
[[self cities] addObject: array];
[array release];
When you alloc an object, its retain count is set to 1:
NSMutableArray *array = [[NSMutableArray alloc] init]; # retain count of array is 1
When you add an object to an NSMutableArray, that object's retain count is incremented:
[[self cities] addObject: array]; # retain count of array is 2
When you release the array, its retain count is decremented:
[array release]; # retain count is now 1
Once your method ends, you still have that array owned by the mutable array [self cities].
Because [self cities] doesn't appear to get released or emptied, this is where you get a leak.
You need to empty or release the mutable array at some point, releasing the objects contained within. If cities is a class property, perhaps release it when the class is released.
EDIT
Fixed init-alloc mistake.

Does this code leak?

I just ran my app through the Leaks in Instruments and I am being told that the following code causes leaks, but I don't see how.
I allocate some NSMutableArrays in my viewDidLoad with this code:
- (void)viewDidLoad {
[super viewDidLoad];
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
}
Then I populate these arrays inside of my viewWillAppear method with the following:
[self.currentCars removeAllObjects];
[self.expiredCars removeAllObjects];
for (Car *car in [self.dealership cars]) {
if ([car isCurrent])
[self.currentCars addObject:car];
if ([car isExpired])
[self.expiredCars addObject:car];
}
And later in the code I release these arrays here:
- (void) viewWillDisappear:(BOOL)animated {
if (currentCars != nil) {
[currentCars release], currentCars = nil;
}
if (expiredCars != nil) {
[expiredCars release], expiredCars = nil;
}
[super viewWillDisappear:animated];
}
Any ideas? Thanks!
Your leak is here:
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
Assuming that you declared property accessores like this:
#property(nonatomic, retain) NSMutableArray *currentCars;
#property(nonatomic, retain) NSMutableArray *expiredCars;
In my opinion, the best way to find leaks (other than using Instruments) is to keep track of the retain count manually.
If you were to do that with for example currentCars, you would find your leak easily. Here is what happens:
self.currentCars = [[NSMutableArray alloc] init];
// The 'init' makes the retain count 1.
// 'self.currentCars = ..' translates to the setCurrentCars: method.
// You probably did not implement that method yourself,
// but by synthesizing your property it is automatically implemented like this:
- (void)setCurrentCars:(NSMutableArray *)array {
[array retain]; // Makes the retain count 2
[currentCars release];
currentCars = array;
}
// In your viewWillDisappear: method
[currentCars release], currentCars = nil; // Makes the retain count 1 so the object is leaked.
The solution is simple. Use this:
NSMutableArray *tempMutableArray = [[NSMutableArray alloc] init];
self.currentCars = tempMutableArray;
[tempMutableArray release];
A little sidenote. You shouldn't release your objects in viewWillDisappear:. The recommended place to do that is dealloc. So your code would be:
- (void)dealloc {
[currentCars release], currentCars = nil;
[expiredCars release], expiredCars = nil;
[super dealloc];
}
The problem is (probably) that you are using the property accessors for the initial setting of the arrays in -viewDidLoad. Since well-implemented property accessors will retain the object, you are getting 1 retain from the +alloc and another retain from assigning it. To fix this, you should release your arrays after assigning them or use [NSMutableArray array] to get an autoreleased one to use for your initial assignments.
Unless you're doing something very odd in currentCars, expiredCars, dealership or cars, no, there's no leak there.
Instruments' pointer to the location of a leak isn't necessarily where the object is actually leaked, per se. If I were to guess, I'd say you're probably neglecting to release either currentCars or expiredCars in your dealloc method.

Why does my array stay empty?

Hi i'm a objC noob. I have a problem filling an NSMutableArray with objects.
for(id p in tmpArray){
Person *person = [[Person alloc] init];
person.usrName = p;
[persons addObject:person]; // after this line the persons
// array is still empty
[person release];
}
Persons is a property NSMutableArray and the problem is that it's empty. Is it the release of the person object too early or have I instanciated it wrong?
You need to initialize your array in the -init method, like this:
NSMutableArray *array = [[NSMutableArray alloc] init];
self.persons = array; // will be automatically retained
// because you're using the property
[array release]; // we alloced - we release it
Don't forget to release it:
-(void)dealloc {
self.persons = nil; // previous value of property 'persons' will be released
[super dealloc];
}
Make sure you've alloced and initialised the array before you try to add things to it