William Robinson
William Robinson

Reputation: 1048

How To Use objectAtIndex In an Array Object

I'm trying to get to grips with Objective C and Cocoa so I may be using the wrong terminology here.

I've made an Objective C class for a deck of cards that is read by my main AppDelegate.h & AppDelegate.m, which has two methods, deckOfCards & pickACard. deckOfCards is just an NSMutableArray with every card type written out in string form, then in pickACard I create a new array like this:

-(void)pickACard
{
    DeckOfCards *newDeck = [[DeckOfCards alloc] init];
    int r = arc4random() % 52;
    NSLog (@"The card you picked is: %@, and there are %i cards left", [newDeck     objectAtIndex:r], [newDeck count]);
    [newDeck removeObjectAtIndex:r];
}

However XCode is saying that I cannot use objectAtIndex & removeObjectAtIndex on this new array, so I can't randomly pick a card and then "remove it from the pack" by removing that part of the array.

This did work when it was all in deckOfCards, but then when it was called by AppDelegate.m it would create a new array so I would get different cards, but never remove more than one from the pack.

I'm going to guess that I'm not creating this new array properly.

Just for more clarity, DeckOfCards.h is this:

#import <Foundation/Foundation.h>

@interface DeckOfCards : NSObject
{
@private
}
-(void) deckOfCards;
-(void) pickACard;

@end

DeckOfCards.m is this:

@implementation DeckOfCards
-(void)deckOfCards
{
NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects:
    @"One of Hearts", @"Two of Hearts"..., nil];
}
-(void)pickACard
{
    DeckOfCards *newDeck = [[DeckOfCards alloc] init];
    int r = arc4random() % 52;
    NSLog (@"The card you picked is: %@, and there are %i cards left",[newDeck     objectAtIndex:r], [newDeck count]);
    [newDeck removeObjectAtIndex:r];
}

@end

Upvotes: 0

Views: 9570

Answers (4)

Peter Hosey
Peter Hosey

Reputation: 96373

However XCode is saying that I cannot use objectAtIndex & removeObjectAtIndex on this new array, …

No, it's saying that you aren't trying to use it on an array.

What you are trying to use it on is a DeckOfCards:

DeckOfCards *newDeck = [[DeckOfCards alloc] init];
int r = arc4random() % 52;
NSLog (@"The card you picked is: %@, and there are %i cards left", [newDeck     objectAtIndex:r], [newDeck count]);
[newDeck removeObjectAtIndex:r];

newDeck being the DeckOfCards that you created on the first line.

A DeckOfCards is not an array:

@interface DeckOfCards : NSObject

A DeckOfCards is simply an object. Not just any object; you have added things to it (the deckOfCards and pickACard methods). But it is not an array, and so does not respond to objectAtIndex: and is not a mutable array and so does not respond to removeObjectAtIndex:, either.

So where do you have an array? Well, you create one in your deckOfCards method:

-(void)deckOfCards
{
NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects:
    @"One of Hearts", @"Two of Hearts"..., nil];
}

But what happens to that array?

Nothing. You create it, and then you drop it on the floor. You aren't returning it to whatever called you; indeed, you can't, since this method returns void.

Momentarily leaving aside the architectural issues, which can be forgiven assuming that you are very new to programming, you need to do several things:

  1. Change the declaration of deckOfCards method to declare that it will return an NSMutableArray *, not void.
  2. Add a line immediately after the creation of the array that returns the array:

    return deckOfCards;
    

    (And note that deckOfCards here refers to the array*.)

  3. Change pickACard to call deckOfCards. That will create the array and (once you've made change #2) return it to you. Declare a variable in pickACard, similar to the one you declared in deckOfCards, and assign the result to deckOfCards to it.

  4. Now that you have an array in pickACard, you can attempt to retrieve one of its items, log it, and remove it. Do this by sending the objectAtIndex: and removeObjectAtIndex: messages to the array, not to the object that isn't an array.
  5. While you're at it, correct the type of r. NSArray indexes are of type NSUInteger, not int.

Now the program should compile, run, and almost work.

Almost? Well, you have one more thing to do.

deckOfCards creates a fresh deck every time; you remove a single card from that deck, but then forget all about that deck, and will be using another fresh deck the next time through.

This is your next challenge: Change the program to create a deck in initialization, and remember and reuse that deck for each subsequent draw. Be sure to handle the case of deck exhaustion (no more cards) correctly, probably by creating a fresh deck at that moment.

(Your first instinct will be to copy and paste. Do that as a first draft only. Then change it so that the deck-creation code is in only one place, a method for that purpose, and you call that method from both places where you need to create a fresh deck.)

I'll give you a hint: Instance variable.


*In your deckOfCards method, you'll see two things named deckOfCards: That method, and the array you declare within it. Two different things with the same name. The compiler doesn't care; methods and variables exist in separate namespaces, so it has no problem knowing which is which at any given moment. But you should probably change one or the other to aid your own understanding, not to mention others' suggestions of what to change (“do this with deckOfCards”, I say—but which deckOfCards do I mean?).

I suggest renaming the array variable. Just naming it array will do for a start. (But only as long as it's a local variable, declared within that method.)

When you make it an instance variable, change the name back to something more specific. _deckOfCards is the common naming convention nowadays—the underscore tells you (again, the compiler doesn't care) that this name is the name of an instance variable.

Upvotes: 3

rdelmar
rdelmar

Reputation: 104092

As the other commenters have said, you have multiple problems with the structure of your app. I've created an example app to show you how to start approaching this problem. First, in your DeckOfCards class you want a property that points to the mutable array that holds your deck of cards -- I called this just deck. Then, you need an init method that not only returns an instance of your DeckOFCards class, but also creates and populates that array of cards. So here is what I put in the DeckOfCards class.

In the .h, just this:

@interface DeckOfCards : NSObject

@property (retain,nonatomic) NSMutableArray *deck;

@end

And, in the .m :

-(id)init {
    if (self = [super init]) {
        NSArray *cardNames = [NSArray arrayWithObjects:@"Two",@"Three",@"Four",@"Five",@"Six",@"Seven",@"Eight",@"Nine",@"Ten",@"Jack",@"Queen",@"King",@"Ace",nil];
        NSArray *suits = [NSArray arrayWithObjects:@" of Hearts",@" of Diamonds",@" of Spades",@" of Clubs",nil];
        self.deck = [NSMutableArray array];
        for (NSString *aCard in cardNames) {
            for (NSString *aSuit in suits) {
                [self.deck addObject:[aCard stringByAppendingString:aSuit]];
            }
        }
    }
    return self;
}

You want to create an instance of this class, and pick cards from inside another class, not from within the DeckOfCards class itself. In this example I've done that in the app delegate (for simplicity ), but it would be better to do this in some controller class. So, I have a property, aNewDeck to keep a reference to that instance (the compiler won't let you use a name that starts with "new", not sure why, or if that's a new thing in mountain lion). I create that new instance in the applicationDidFinishLoading method, and then have an IBAction connected to a button that picks the card. Notice that I changed the random number generator to not use 52, but the count of the array -- if you don't do that, you'll be picking numbers that are bigger than the array as it gets smaller. You should notice, that to get access to the array, I use aNewDeck.deck which is the way to access that property, deck, of the instance aNewDeck. Here's the .h file:

@class DeckOfCards;
#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;
@property (strong) DeckOfCards *aNewDeck;

-(IBAction)pickACard:(id)sender ;

@end

An here's the .m:

#import "AppDelegate.h"
#import "DeckOfCards.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    self.aNewDeck = [[DeckOfCards alloc] init];
}

-(IBAction)pickACard:(id)sender {
    if (self.aNewDeck.deck.count != 0) {
        int r = arc4random() % [self.aNewDeck.deck count];
        NSLog (@"The card you picked is: %@, and there are %li cards left", [self.aNewDeck.deck  objectAtIndex:r], [self.aNewDeck.deck count] - 1 );
        [self.aNewDeck.deck removeObjectAtIndex:r];
    }else{
        NSLog(@"Game Over!");
    }
}

I hope this helps you get started.

Upvotes: 2

Carl Veazey
Carl Veazey

Reputation: 18363

I see a few issues here. The immediate cause of your compiler issue is that you are trying to call NSMutableArray methods on an instance of DeckOfCards. Instead, you want toc call these methods on the NSMutableArray returned by the method-[DeckOfCards deckOfCards]. It would look something like this:

DeckOfCards *newDeck = [[DeckOfCards alloc] init];
NSMutableArray *deckArray = [newDeck deckOfCards];
int r = arc4random() % 52;
NSLog (@"The card you picked is: %@, and there are %i cards left",[deckArray objectAtIndex:r], [deckArray count]);
[deckArray removeObjectAtIndex:r];

But this leads us to our next issue - -deckOfCards doesn't actually return anything - its return type is defined as void. So in order to get that NSMutableArray instance back to the rest of your program you're going to have to return it, and define the method with the appropriate return type. If we were just to address this issue, we'd change your -deckOfCards method like so:

-(NSMutableArray *)deckOfCards
{
    NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects:
        @"One of Hearts", @"Two of Hearts"..., nil];

    return deckOfCards;
}

OK, so that should compile now. But there are some serious errors here. I'm afraid I don't have much more time to put into this answer right now, but I suggest you read some introductory Objective-C, focusing on the difference between objects and classes, and the lifetime of objects and memory management. Essentially, nothing is ever persisted. No matter how many times you call -pickACard on any instance of DeckOfCards, you will always get a separate instance instantiated which will then get a brand new NSMutableArray instantiated. But none of these objects are ever retained (assuming you're using Automatic Reference Counting) and they all disappear immediately. So you will always, every time, get a brand new 52 card deck from -pickACard.

Hopefully this helps. With some quality time put into reading and learning the fundamentals of Objective-C and Object Oriented Programming, you will be well on your way to making this program work like you want. Let me know if you have questions and I'll try to answer them.

Upvotes: 4

user529758
user529758

Reputation:

DeckOfCards is not a subclass of NSMutableArray and it doesn't implement the - objectAtIndex: and - removeObjectAtIndex: methods either - so it's fine that you can't use these methods.

Upvotes: 1

Related Questions