Storing objects in an NSMutableArray

Discussion in 'Mac Programming' started by larswik, Jul 25, 2011.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    I have a class called Body. In this class I have 3 int variables for weight, height, age. In main.m I instantiate Body *personOne and then use arc4Random to auto fill in the 3 ivars (not worried about floats, it's all integers)

    That part I have down just fine, but I want to create something like 20 people and store them in an NSMUtableArray. My hopes are to use a for loop to auto create 20 people with different stats for the 3 ivars and then add them to an an NSMutableArray so I can adjust weight, age and so on.

    I know that NSMutableArrays can only store objects and not Integers. But what if integers are embedded in personOne which is an object then you are storing the personOne object in an NSMutableArray, right? Or must they all be converted to NSNumber to store that way?

    I have been scratching my head most of the night trying to figure out how to create 20 persons objects with a for loop, add the 3 values and store them into an NSMutableArray.

    Is this possible, Body *person = [[Body alloc] init]; for creating 20 persons and incrementing i within a for loop?

    Thanks
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    The internal details of an object make no difference to a NSMutableArray (or any other Cocoa collection class). The array stores a pointer to the object. It does not need to care about what is inside the object.
     
  3. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #3


    No. Remember that an Objective-C is not a C array and you cannot use C array syntax with it.

    You'll need to repeatedly alloc and init a new Body object each time, and add it to your mutable array using addObject:.
     
  4. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #4
    Hummmm... If I use a for loop and did 'Body *person1' for 20 iterations would I end up with 20 personOne or the same one rewritten in the NSMutableArray? (I am not in front of Xcode so I can't test it till tonight).

    "Remember that an Objective-C is not a C array and you cannot use C array syntax with it." When I read that I remember what Chown33 said like -"Everything is represented by an object". So by a C style array I can store integers but in Objective - C my integers should be represented by NSNumber which turns my integers in to objects?
     
  5. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #5
    Depends. If did this, then you'd end up with 20 pointers to the same Body object.
    Code:
    NSMutableArray *array = [[NSMutableArray alloc] init];
    Body *person = [[Body alloc] init];
    for (NSInteger i = 0; i < 20; i++) {
      [array addObject:person];
    }
    But if did this, then you'd end up with 20 different pointers to different Body objects.
    Code:
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for (NSInteger i = 0; i < 20; i++) {
      Body *person = [[Body alloc] init];
      [array addObject:person];
    }
    Do you see the fundamental difference? And no, it's not because the declaration of person was moved inside the for loop.


    An NSArray stores values of type id and can therefore only store pointers to objects. If you want to store integers (or doubles or chars, etc) in an NSArray you need to "box" them into an NSNumber objects and store a pointers to the NSNumber objects instead.
     
  6. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #6
    I did the correct way using the for loop. I keep forgetting that I am working with pointers to objects.

    Here is the code for the for loop. I could not find an easier way at my current skill level to simplify it. But the end result is what I was looking for. I was first trying to fill in the 5 int values for the 'fighterOne' object. Then I tried to just store the FighterOne pointer in an NSMutableArray and later I would be able to access that array index for that current fighter and get the values. In the end I stored each item in its own array and 'Boxed" the int in an NSNumber.

    Code:
    for(int i = 0; i < goodArmySize; i++){
            fighterLevel = arc4random() %12 + 1; // rolls a random to set the level of the fighter
            
            fighterClass *fighterOne = [[fighterClass alloc]init]; // instantiate a fighter
            
            // fills in the unique information for each fighter based off of the random roll
            [fighterOne setFighterId:i];
            [fighterOne setLevel: fighterLevel];
            [fighterOne setHitPoints: [getStats findHitPoints:[fighterOne level]]];
            [fighterOne setArmorType:[getStats findArmorType:[fighterOne level]]];
            [fighterOne setAdrenalSpeed:[getStats findAdrenalSpeed:[fighterOne level]]];
    
            // 5 different NSMutableArray's to store each object
            [goodGuyName addObject:[NSNumber numberWithInt:[fighterOne fighterId]]]; 
            [goodGuyLevel addObject:[NSNumber numberWithInt:[fighterOne level]]]; 
            [goodGuyHitPoints addObject:[NSNumber numberWithInt:[fighterOne hitPoints]]]; 
            [goodGuyArmorType addObject:[NSNumber numberWithInt:[fighterOne armorType]]]; 
            [goodGuyAdrenalSpeed addObject:[NSNumber numberWithInt:[fighterOne adrenalSpeed]]]; 
            
            [fighterOne release];
        }
    
     
  7. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #7
    You're really close. Ditch the 5 co-arrays and just add fighterOne to an NSMutableArray inside the loop after its attributes are set. Once the loop is done the array will hold all of your fighters. Each fighter will contain the associated attributes. Maybe this will help: fighterClass (rename this now, maybe to larFighter, etc.) is an object (an NSObject, specifically) just like NSNumber. If you can put an NSNumber in a NSMutableArray, you can put a fighterClass in one, too.

    -Lee
     
  8. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #8
    That is where I was having the problem, accessing the ivars.

    I swapped out the 5 NSMutable arrays with 1 and used this code
    Code:
    [goodGuyName addObject:fighterOne];
    But I an at a loss as to access those ivars to print them out. I knew this was not going to work but it shows how I was thinking.
    Code:
    NSLog(@"%@", [goodGuyName objectAtIndex:0]);
    How do you reach further into fighterOne Object to get the individual instance variables?

    -Lars
     
  9. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #9
    Forget NSArrays for the moment. Given a local variable fighterClass *fighterOne containing a valid pointer to a fighterClass object, how would you get the current value of level property of that object?

    Now think back to NSArrays. Given a instance variable NSArray *fighters containing a valid pointer to an array of 5 valid pointers to fighterClass objects, how would you store the pointer at index 0 of the fighters array into the fighterOne local variable?

    Lastly how would you combine this to access the level property of the object who's pointer is stored in index 0 of the fighters array?
     
  10. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #10
    If I understood this correctly I would access a property like this to write the results
    Code:
     NSLog(@"Level is: %d", [fighterOne level]);
    That is the black part I am trying to figure out. NSArray *fighter at Index 0 is holding a pointer to the fighterOne pointer. Which in turn is pointing to the 5 NSNumber variables that I "boxed".
    This seemed logical but it is not working
     
  11. gnasher729, Jul 26, 2011
    Last edited: Jul 26, 2011

    gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #11
    The "objectAtIndex" method doesn't know what's the type of your object, so it returns "id". "id" means to the compiler: "This is a pointer to some object, but I don't know which one". You can send messages to an "id" (but the compiler has no chance to do any checking that you send a message that the object actually understands, because it doesn't know what kind of object it is), but you can't access member variables.

    There are two ways around that: Assign the id to a variable that has the right pointer type, or cast the id to the right pointer type. So one of these two:

    Code:
    fighterClass* aFighter = [goodGuyName objectAtIndex:0];
    NSLog (@"Level = %d", [aFighter level]);
    Code:
    NSLog (@"Level = %d, [(fighterClass *) [goodGuyName objectAtIndex:0] level]);
    The first one looks more readable to me.
    BTW. If your class has a method
    Code:
    - (NSString*)description
    then for example
    Code:
    NSLog (@"Figheter %@", aFighter);
    will print that description; you can also use that in the debugger. Very useful.

    When you wrote your NSLog statement, that just couldn't work. If you find it hard, start with something simple and change it. Like:

    NSLog (@"Level = %d", [someobject level]);

    looks right except that you don't have "someobject". Now type in a separate line what you want instead of someobject, for example

    [goodGuyName objectAtIndex:0]

    But that doesn't work, because it has the wrong type. So:

    (fighterClass*) [goodGuyName objectAtIndex:0]

    Then you cut the whole thing (command-X), select "someobject" and paste.
     
  12. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #12
    So it seems what you are doing is assigning the object at that Index back into an object. This way you can easily access it. I understand this.
    Code:
    fighterClass* aFighter = [goodGuyName objectAtIndex:0];
    NSLog (@"Level = %d", [aFighter level]);
    I have not heard of the 'description' method. I will read up on it. Most of the error I get these days are incompatible pointer types.

    Thanks for the explanation. I do try what you said about breaking things down to the simplest part first and work from there.

    -Lars
     
  13. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #13
    [goodGuyName objectAtIndex:0] returns an id, which is an untyped pointer to an object. If you know that responds to level, you can pass level.
    [[goodGuyName objectAtIndex:0] level]

    Message passes are frequently nested. For example, when you send alloc to a Class object, you always send some sort of init message to the object whose pointer is returned.

    It's a bit abstract, but you may want to try to get a better idea of where in memory things live and what a pointer does. A pointer variable is a small block of memory that stores a value, similar to how a float or int holds a value. Instead of storing 3 or 4.63 a pointer variable holds an address in memory. You can change the value stored in a pointer variable using = like any other variable, but you normally don't set it to a fixed address because generally things you want to point to won't be at the same memory location every time your program runs. That means we normally set the value of a pointer variable to the result of a function that returns an address, such as an alloc/init, the C malloc family, or in plain C the result of using the & operator to get the address of a variable. The pointer variable is NOT the object it points to. The object is sitting out in memory, and we use its address (which we might be storing in a pointer variable) to send it messages.

    -Lee
     
  14. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #14
    Thanks Lee. I do get pointers like any other variable that holds a value in memory it holds an address to an object in memory. When I was learning C I passed the address in to a function &ptr, and would swap out the value with the *ptr before it exited the function. A variable pointer is just an address, right? It won't matter if it is a pointer to an NSArray or an NSNumber. I get the error messages back because it checks what it is pointed to. So if I have an NSString at index 0 of an NSArray I can't say for example 'int i = [myNSArray objectAtIndex:0];

    If I have this line of code.
    Code:
    NSLog(@"LVL: %d",[[goodGuyName objectAtIndex:0] level] );   
    I receive a warring saying "Conversion specifies type "int" but the argument type 'NSUInteger' (aka 'unsigned long')" and a second warrning under it saying "Multiple methods named level found".

    It seems that since I "boxed" the int into an NSNumber I would have to "unbox" it back to an int again before I can display it with %d. or use the %@ for the NSNumber that was stored.

    By the way, is this message passing you wrote "[[goodGuyName objectAtIndex:0] level]"? It seems like you missed a step or passed through the first object FighterOne to get to the level variable.

    Thanks again for taking the time. Last year this time I was asking basic C questions. I am slow learning with somethings :)
     
  15. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #15
    Why are your ivars NSNumbers? int should be fine. If you changed it the code in your last post would be fine. You'll still get a warning, but it will work fine. You could add a cast to get rid of it if desired. You could create a category on NSArray that adds a fighterAtIndex: method that calls objectAtIndex: and returns the result cast to your class type. You could then call that method, nest it in a call to an accessor, and be warning-free.

    -Lee
     
  16. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #16
    The compiler doesn't know what kind of object [goodGuyName objectAtIndex:0] is. So if there is more than one class having a method named level, it doesn't have an idea which one is going to be called. I think NSMenuItem might have a method named "level" returning an NSUInteger, and the compiler doesn't know that you didn't put any NSMenuItem* into your array.

    So just add the line putting the object pointer into a local variable.
     
  17. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #17
    Thanks Guys! I did not think of that. I turned my ivars into NSNumbers because I was not sure that I could store int in an NSArray. I "Boxed" Then to be safe. All this kind of reminds me of the last Leonardo DiCaprio movie. Inception. My Level variable, buried in my fighterOne object buried in my NSArray. A dream within a dream within a dream :)

    -Lars
     
  18. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #18
    Don't over-think this Lars. An NSArray stores values of type id, so it can only store pointers to objects. But those objects themselves can store whatever they like.

    Remember you're always dealing with pointers to objects, not the objects themselves. Variables hold pointers to objects, not the objects themselves. NSArrays hold pointers to objects, not the objects themselves. They key/value pairs in NSDictionaries hold pointers to objects, not the objects themselves. When you assign one variable to another, you're only assigning a pointer to the object, not the the object itself. Etc, etc, etc.
     

Share This Page