PDA

View Full Version : Populating and accessing NSMutableArray




:: ultranol ::
Apr 19, 2009, 05:03 PM
Hello everyone. I've been wanting to develop a game for iPhone for a while, and after watching the iPhone SDK 3.0 I decided I would have a go. Having been a programmer for 6 years in the past, having even a published game in the market, I thought it would be possible.

However, I didn't mentioned that the programming experience that I had was mostly with Web programming, and the shipped game was developed by me in Flash ActionScript. You have already figured it out, right? It's being hard as hell to get even the most basic things running, since I've never seen C in my life. I only happen to know a little about Java, but this isn't helping.

I've seen all Getting Started videos, of course, and a lot of Objective-C tutorials... but I think it's hard to get the concept of everything like this, in one or two days.

Here's my problem (for now): here's a snippet of my custom class named Game ("Game.h" file):

@interface Game : NSObject {
int matchesPlayed;
NSMutableArray* players;
NSMutableArray* deck;
}

+ (Game*)startGame;
- (id)initGame;
- (NSMutableArray*)createDeck;


and here's my startGame method in the "Game.m" file:

- (NSMutableArray*)createDeck
{
NSMutableArray* tmpDeck;
Card* tmpCard = nil;
NSString* ranks = @"4567QJKA23";
NSString* suits = @"DSHC";
for (int count = 0; count < 40; count++) {
tmpCard = [Card createCardWithRank:[ranks substringWithRange:NSMakeRange(count % 10, 1)]
Suit:[suits substringWithRange:NSMakeRange(floor(count) / 10, 1)]];
NSLog(@"card %@", [tmpCard getCardValue]);
[tmpDeck addObject:tmpCard];
}
for (Card* obj in tmpDeck) {
NSLog(@"hello world");
}
return [tmpDeck autorelease];
}


The first NSLog output is OK, it shows all 40 cards and its suits (that's what the getCardValue does), but the second NSLog output, when I try to access the Cards from inside the array, doesn't work. It doesn't even enter the "for", signaling that the tmpDeck array is probably empty.

If I added objects to the tmpDeck array (with the tmpDeck addObject method), why is there nothing inside?

The weird thing is, if I put a "for" loop to walk through the 40 items of the tmpDeck array, it shows (null) for everyone... but even if I try to go to the fiftieth item, that doesn't exists, it doesn't give me an error... it just shows (null). Is this the normal behavior of an array? I'm used to get an "index out of bounds" error in other programming languages.

I'm trying to keeping it to the basics, first writing the game logic and testing it through the NSLog, but I can't even do that! I'm very frustrated... I'm struggling to conform myself that programming for the iPhone is just for the real programmers, and not Flash-Web-programmers like me... :(

It would be great if someone could explain what am I doing wrong here...

Thank you very much.



eddietr
Apr 19, 2009, 05:40 PM
So one issue is that you are declaring a pointer to an NSMutableArray, but you never actually created the array.

You have an instance variable for your deck in your Game class. I assume you are setting that variable somewhere else (like in the init?)

:: ultranol ::
Apr 19, 2009, 06:31 PM
So one issue is that you are declaring a pointer to an NSMutableArray, but you never actually created the array.

You have an instance variable for your deck in your Game class. I assume you are setting that variable somewhere else (like in the init?)

Wow man, thanks. This solved the problem:


NSMutableArray* tmpDeck = [NSMutableArray arrayWithCapacity:0];

Maybe there's hope after all. :)

One thing I don't get 100% still is the necessity of doing the alloc and release statements... I hope those SDK tools can point me if there is wrong with my memory allocation because this is new to me.

Thank you again for pointing me the obvious.

eddietr
Apr 19, 2009, 06:43 PM
Wow man, thanks. This solved the problem:


NSMutableArray* tmpDeck = [NSMutableArray arrayWithCapacity:0];

Maybe there's hope after all. :)



Yeah, you're right that there is more involved here than in your typical web development. But at the same time it's not rocket science once you get used to it. But just realize it takes time to get used to this stuff.

BTW, you get a little performance improvement if you set the actual expected size (in you case 40, right?) in [NSMutableArray arrayWithCapacity:]. This way the array doesn't need to be resized with every insertion. It will work either way, but this is just a little more efficient.

Also, keep in mind this array that you get back is already autoreleased.


One thing I don't get 100% still is the necessity of doing the alloc and release statements... I hope those SDK tools can point me if there is wrong with my memory allocation because this is new to me.

Thank you again for pointing me the obvious.

Definitely take the time to read the memory management guide for Cocoa. You will save yourself a ton of time and frustration if you understand that first.

Good luck!

BlackWolf
Apr 20, 2009, 10:07 AM
Wow man, thanks. This solved the problem:


NSMutableArray* tmpDeck = [NSMutableArray arrayWithCapacity:0];

Maybe there's hope after all. :)

One thing I don't get 100% still is the necessity of doing the alloc and release statements... I hope those SDK tools can point me if there is wrong with my memory allocation because this is new to me.

Thank you again for pointing me the obvious.

you should really read about the memory managment stuff. it's hard if you are not used to that kind of stuff, but you will save yourself a lot of trouble. if you receive your first EXC_BAD_ACCESS error your code is probably so screwed up by then that it'll be really hard to get it right.

the necessity is simple memory managment. when you create an object the object takes up memory. every object is born with a retain count of 1. you can increase the retain count with "retain" or decrease it with "release" or decrease it some time in the future with "autorelease". when the retain count reaches 0, the object is destroyed.

that's the really really really short, simple version. just to show you why you need this. if you never release objects and you create a large application, everything you ever create will stay in the memory until your memory is full which simply results in your app crashing.

since I recently learned all that stuff myself and I know how hard it can be you can PM me with a question if you want, but ONLY if you read through apple's memory managment pages and you have a specific question (not like "how does memory managment work" or something like that). I can't guarantee I'll know the answer but I hope so :p

:: ultranol ::
Apr 20, 2009, 01:33 PM
since I recently learned all that stuff myself and I know how hard it can be you can PM me with a question if you want, but ONLY if you read through apple's memory managment pages and you have a specific question (not like "how does memory managment work" or something like that). I can't guarantee I'll know the answer but I hope so :p

I read the basic stuff and now I think I get the necessity of alloc, release, autorelease and stuff. Regarding "retain" and "copy" clauses, I can understand it from the examples that are given, but I still can't quite see them in my code. Probably over time I will get the hang of it.

Thank you for offering help, I really appreciate it! Watch out, because I may actually need it :)

BlackWolf
Apr 20, 2009, 04:18 PM
I read the basic stuff and now I think I get the necessity of alloc, release, autorelease and stuff. Regarding "retain" and "copy" clauses, I can understand it from the examples that are given, but I still can't quite see them in my code. Probably over time I will get the hang of it.

Thank you for offering help, I really appreciate it! Watch out, because I may actually need it :)

well, you normally use retain when you get an object (for example as a method parameter) and you need it for later.
let's say you have a method that takes a UIView as a parameter and you want to store that UIView in an instance variable. if you just store it it could happen (in fact it is very likely) that the view gets a "release" message SOMEWHERE in your app - which would mean that the view is destroyed and unusable for your class. so, to make sure that the view stays alive even if some other part of your app sends it a release message, you use retain and store it in an instance variable. and if you are done with it you send it a release message. if your class was the last part of your app that needed that view the retain count is gonna drop to 0 and the view is released. if some other part of your app sent it a retain too but didn't release it until then, the view stays alive for those parts of the code to use.

as for copy ... I mostly use it for instance variables where I want to make sure my object cannot be changed from the outside. for example, if I take an NSString as a method parameter and store it in an instance variable, it could happen that some other part of my app changes that string - which would change it in my instance variable as well. to prevent that, I create my own copy of that string for my class.

have fun learning :D

:: ultranol ::
Apr 20, 2009, 09:07 PM
well, you normally use retain when you get an object (for example as a method parameter) and you need it for later.
let's say you have a method that takes a UIView as a parameter and you want to store that UIView in an instance variable. if you just store it it could happen (in fact it is very likely) that the view gets a "release" message SOMEWHERE in your app - which would mean that the view is destroyed and unusable for your class. so, to make sure that the view stays alive even if some other part of your app sends it a release message, you use retain and store it in an instance variable. and if you are done with it you send it a release message. if your class was the last part of your app that needed that view the retain count is gonna drop to 0 and the view is released. if some other part of your app sent it a retain too but didn't release it until then, the view stays alive for those parts of the code to use.

OK, this is what I don't get... in this example, I would store the UIView instance in an instance variable of another object and I would "retain" it. Somewhere in my app it says "release" to this UIView instance, and because I instructed it to be "retained", it is not released. Let's say there were 4 "release" instructions that happened this way (this is possible, right?). Then, I call a method that releases the object where UIView is stored, let's say, when the game is over. How can the program tell the difference between MY explicit call to release and the other calls that were made in other parts of my app? :confused:

I'm posting this here because I think other people can find this useful... if I'm wrong we can discuss this via PM...

BlackWolf
Apr 21, 2009, 10:06 AM
OK, this is what I don't get... in this example, I would store the UIView instance in an instance variable of another object and I would "retain" it. Somewhere in my app it says "release" to this UIView instance, and because I instructed it to be "retained", it is not released. Let's say there were 4 "release" instructions that happened this way (this is possible, right?). Then, I call a method that releases the object where UIView is stored, let's say, when the game is over. How can the program tell the difference between MY explicit call to release and the other calls that were made in other parts of my app? :confused:

I'm posting this here because I think other people can find this useful... if I'm wrong we can discuss this via PM...

well ... you say there are 4 release instructions "that way". that makes me believe that you think you create the object once, and then release it 4 times. this will make your app crash since you "overreleased" that object. the total number of retains/allocs/copies/inits and releases/autoreleases for an object has to be balanced in your app!

but let's say you would retain the view 4 times AND release it 4 times (don't know why you would do that, but whatever :D), and after that the view is released in the class you stored (and retained) it. the program doesn't see any difference in your retains/releases, you are right with that. what the app does: for every retain, it increases the retain count. so when you created your object(1) and retained it 4 times(4), the retain count is 5. you release it 4 times after that, which gives you a retain count of 1. NOW you release the object in your class - the retain count drops to 0, the object is destroyed.

so the app doesn't make any difference WHERE you retain or release. it simply counts how many times you retained and how many times you released. if a method or class retains an object that simply means "I will need this later, don't destroy it." when a method or class releases it, that means "I'm done with it, do whatever you want." - when the retain count drops to zero, that means every method or class or whatever that once needed that object told the app that it doesn't need it anymore and so the object can be destroyed.

it's really hard to describe this stuff without code, next time I stumple upon a nice code that describes this I'll post it

edit: ok, here are some parts of a class I wrote. I don't guarantee everything is nice and clean in there, but I guess so.
that class has an instance variable NSMutableArray called cards. what you should know about arrays is that when you add an object to an array it is automatically retained, and when you remove it from the array (or you remove the array) it is automatically released. with this in mind here we go ...

-(void)addCard:(Card *)newCard {
[self.cards addObject:newCard];
}

in this method I get an object "newCard" and i want to keep it around since I need it for later. because it is automatically retained when I add it to the array I don't really have to do anything. If I would store that object in an own instance variable I would have to retain it!

-(void)addCardWithString:(NSString *)newCardStringValue {
Card *newCard = [[Card alloc] initWithString:newCardStringValue];
[self.cards addObject:newCard];
[newCard release];
}

here I only get a string object (that I don't need later, so I do NOT have to retain it!). with this I create a new card object (retain count of 1). after I added it to my array (retain count 2) I release it. since I only need it in the array and it was retained by the array I can release it, giving it a retain count of 1 again. now, when I remove it from the array the retain count will drop to 0 and it will be destroyed.
btw: this is exactly why you have to keep an eye on memory managment. if I would not have released newCard here, it would still have a retain count of 2. so, when I remove it from the array later it would still have a retain count of 1 - and not get destroyed, thus staying in the memory until your app terminates. this might not be that bad for a few objects, but imagine an array with a thousand objects or something and it can easily crash your app.

-(Card *)extractRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard = [[self.cards objectAtIndex:indexOfRandomCard] retain];
[self.cards removeObjectAtIndex:indexOfRandomCard];

return [randomCard autorelease];
}

this is pretty interesting regarding memory-managment, but also a little difficult.
for this you need to understand auto-releasing. every application has an auto-release pool. you can add an object to it by sending it the autorelease message. what this does is: it keeps the object around until your code has finished working (so, for example, until your code doesn't do anything because it waits for user input) and then releases it.
You mostly need this for objects that you want to return but don't need anymore, like in the example above.
let's do it step by step:
1) I get a random object from my array. let's say that object has a retain count of 1. It could have a higher retain count if some other part of my app retained it. but if some other part of my app did, IT is responsible for releasing it, so I don't care.
2) I retain the object, which gives it a retain count of 2
3) I remove the object from my array. retain count 1.
4) I return the object and autorelease it. what this means: whoever called this method will get the object back since it is not released instantly (like it would have been if I had used release), so it still has a retain count of 1! whoever receives the object can use it and even retain it. when the autorelease pool is "emptied" next time (which is guaranteed to be after the receiver of the object finished working with it, since the autorelease pool is not emptied as long as your code is doing stuff) the object will be released. if the method that received the object didn't retain it, the retain count will be 0 and the object will be destroyed. if it DID retain it, the retain count will be 1 and the object will NOT be destroyed, which gives that method (the one that received the object) the possiblity to store it or do something else with it. of course if the method that received the object did retain it, IT is responsible for also releasing it. when it finally does, the retain count will drop to 0 and the object will be destroyed.

ok, I admit, this might have been confusing. it's not as easy to describe as I thought it would be. if you still have questions, ask.

:: ultranol ::
Apr 21, 2009, 06:27 PM
well ... you say there are 4 release instructions "that way". that makes me believe that you think you create the object once, and then release it 4 times. this will make your app crash since you "overreleased" that object. the total number of retains/allocs/copies/inits and releases/autoreleases for an object has to be balanced in your app!

OK, I think now I get it: let's say I create this object (A), and I store it in an instance variable of another object (B). The retain count of object A is 1. If object C accesses object B and through an accessor method it gets object A to do something. When object A is inside a method of object C, the retain count of object A is still 1. I would want to retain object A inside object C, because if object B gets released, object A would be also released, and object C would lose its reference... so I use the retain and the retain count is now 2. If object B is released the release count of object A is decreased to 1 and therefore object C can still work with it.

Am I getting this right?


-(Card *)extractRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard = [[self.cards objectAtIndex:indexOfRandomCard] retain];
[self.cards removeObjectAtIndex:indexOfRandomCard];

return [randomCard autorelease];
}

this is pretty interesting regarding memory-managment, but also a little difficult.
for this you need to understand auto-releasing. every application has an auto-release pool. you can add an object to it by sending it the autorelease message. what this does is: it keeps the object around until your code has finished working (so, for example, until your code doesn't do anything because it waits for user input) and then releases it.
You mostly need this for objects that you want to return but don't need anymore, like in the example above.
let's do it step by step:
1) I get a random object from my array. let's say that object has a retain count of 1. It could have a higher retain count if some other part of my app retained it. but if some other part of my app did, IT is responsible for releasing it, so I don't care.
2) I retain the object, which gives it a retain count of 2
3) I remove the object from my array. retain count 1.
4) I return the object and autorelease it. what this means: whoever called this method will get the object back since it is not released instantly (like it would have been if I had used release), so it still has a retain count of 1! whoever receives the object can use it and even retain it. when the autorelease pool is "emptied" next time (which is guaranteed to be after the receiver of the object finished working with it, since the autorelease pool is not emptied as long as your code is doing stuff) the object will be released. if the method that received the object didn't retain it, the retain count will be 0 and the object will be destroyed. if it DID retain it, the retain count will be 1 and the object will NOT be destroyed, which gives that method (the one that received the object) the possiblity to store it or do something else with it. of course if the method that received the object did retain it, IT is responsible for also releasing it. when it finally does, the retain count will drop to 0 and the object will be destroyed.

This example is the best one I've seen until now. It makes perfect sense. A thought: you are only using the retain clause here because you are removing the object from the array, right? If you were only checking its content you wouldn't need to retain it because the retain count would not be decreased, like this:


-(Card *)getRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard = [self.cards objectAtIndex:indexOfRandomCard];
return [randomCard autorelease];
}

Another thing: I've seen examples of the retain clause like this:


-(Card *)extractRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard;
[randomCard retain];
randomCard = [self.cards objectAtIndex:indexOfRandomCard];
[self.cards removeObjectAtIndex:indexOfRandomCard];
return [randomCard autorelease];
}


Can I do it this way? Or I can't because the variable didn't yet receive the object I would want to retain?

Thanks for the lecture :)

BlackWolf
Apr 22, 2009, 10:15 AM
OK, I think now I get it: let's say I create this object (A), and I store it in an instance variable of another object (B). The retain count of object A is 1. If object C accesses object B and through an accessor method it gets object A to do something. When object A is inside a method of object C, the retain count of object A is still 1. I would want to retain object A inside object C, because if object B gets released, object A would be also released, and object C would lose its reference... so I use the retain and the retain count is now 2. If object B is released the release count of object A is decreased to 1 and therefore object C can still work with it.

Am I getting this right?

well, I guess mostly :D
here some additional thoughts on that:
if you are in a method in object C, only retaining object A would not help you. so if you do something like
[[object B objectA] retain];
that isn't helping you much. if object B is destroyed objectA stays alive, but you have no way of speaking to it anymore. you need to store it somewhere:
ObjectA something = [[objectB objectA] retain];
this way you have a way of accessing objectA even if B gets destroyed.
but remember this: if your method in object C is running and you only need objectA inside that method, you do NOT have to retain it. objectB cannot be destroyed while your method is running. when the method of object C is executed, that part of the code is running, no other part of the code, so your object can't just vanish without some code telling it to be released. but AFTER your method finished the object could be destroyed. so if you need to save objectA (or B) persistently, then store it in an instance variable and retain it!

besides that I think your example is probably correct, though it would be much easier to judge a real code example :D


This example is the best one I've seen until now. It makes perfect sense. A thought: you are only using the retain clause here because you are removing the object from the array, right? If you were only checking its content you wouldn't need to retain it because the retain count would not be decreased, like this:


-(Card *)getRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard = [self.cards objectAtIndex:indexOfRandomCard];
return [randomCard autorelease];
}

well, yes I am only using retain because I am removing it from the array. what's wrong with the code you posted is the autorelease. when I get the object out of the array, it is not retained, so I have to assume it's retain count is 1. so if I autorelease it the object would probably get destroyed (a little later). but if I want it to be destroyed, I should do so by removing it from the array, not by releasing it. without the autorelease your code is correct though.


Another thing: I've seen examples of the retain clause like this:


-(Card *)extractRandomCard {
srandom(time(NULL));
int indexOfRandomCard = random()%[self.cards count];
Card *randomCard;
[randomCard retain];
randomCard = [self.cards objectAtIndex:indexOfRandomCard];
[self.cards removeObjectAtIndex:indexOfRandomCard];
return [randomCard autorelease];
}


Can I do it this way? Or I can't because the variable didn't yet receive the object I would want to retain?

Thanks for the lecture :)
your app would probably crash. the retain count does not belong the the name of the variable (randomCard), it belongs to the object itself. an empty variable is nothing but a placeholder - you can't retain it or send any other message to it.
BUT if you put the [randomCard retain]; after the randomCard = [...]; line this would work fine, you don't have to do it like I did and use retain in the same line you get the object out of the array, you can retain an object everytime you want.

my pleasure ;-)

:: ultranol ::
Apr 22, 2009, 11:32 AM
Yeah, regarding the [autorelease] that I exemplified and you corrected me, coincidentally I tried to use that in my code and after some crashes I noticed by myself that it was wrong.

Man, I feel a lot more sure about developing now. I was almost giving up... Let's see how things turn out when I start to deal with windows, views... :)

Once again, thank you. You are a gentleman and a scholar!