Problems with variables in Obj-C

Member
Posts: 29
Joined: 2006.07
Post: #1
I've been playing with Objective-C and Cocoa for quite a while now, but I'm just now beginning to really learn. Anyways, I made a little test program to read tile values from a string (mapl) and use a 'for' loop to draw each tile in the string across the top row of my NSView. A 1 represents a grass tile and a 2 represents a tree tile.

However, when I run it, everything is covered in tree (2) tiles, instead of being grass, tree, grass, tree (1212) like in the string. Any ideas of what is wrong? I think my switch statement is executing all of the code for some reason instead of just executing the corresponding case.

Code:
#import "MyView.h"

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
    }
    return self;
}

- (void)drawRect:(NSRect)rect
{

    NSImage   *tile01      = [[NSImage imageNamed:@"T01"] retain];  // Dimension tiles
    NSImage   *tile02      = [[NSImage imageNamed:@"T02"] retain];
    
    NSString  *nextTile    = [[NSString alloc] init];  // Variable to hold next tile in the string
    NSString  *mapl        = [[NSString alloc] init];  // Actual tile data
               mapl        = @"1212121212121";
               int           nextTileNum;  // Integer version of nextTile to work with switch?

    NSRect bounds = [self bounds];

    [[NSColor blackColor] set];
    [NSBezierPath fillRect:bounds];

    NSPoint tilePoint;
    tilePoint.x = (0);
    tilePoint.y = (160-16);
    
    int i;
    
    for ( i = 0; i < 13; i++ ) {
        nextTile = [mapl substringWithRange:NSMakeRange(i,1)];
        nextTileNum = [nextTile intValue];  // Convert nextTile to an integer
        switch (nextTileNum) {
            case 1:
                tilePoint.x = (i * 16);
                tilePoint.y = (144);
                [tile01 dissolveToPoint:tilePoint fraction:1.0];
                NSLog(@"Drew Tile 1.");
            case 2:
                tilePoint.x = (i * 16);
                tilePoint.y = (144 - 16);
                [tile02 dissolveToPoint:tilePoint fraction:1.0];
                NSLog(@"Drew Tile 2.");
        }
        
    }
}
@end

and also :
Code:
[Session started at 2006-10-01 23:03:18 -0400.]
2006-10-01 23:03:19.933 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.951 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.955 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.955 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.956 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.956 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.956 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.956 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.956 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.957 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.959 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.960 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.981 OpenEdit[400] Drew Tile 2.
2006-10-01 23:03:19.982 OpenEdit[400] Drew Tile 1.
2006-10-01 23:03:19.982 OpenEdit[400] Drew Tile 2.

OpenEdit has exited with status 0.
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #2
A few suggestions:
Do yourself a favor and only load the images once rather than every time you draw. Also, tile01, tile02, nextTile, and map1 are all leaked every single call to draw. That's... not good. For nextTile and map1, you have the further problems that you're allocating space, then throwing them away immediately by assigning them to something else. Whenever you use = on a variable, that's essentially throwing away what used to be there, and in this case, it leaks memory.

For your loop, instead of having just 13, you should use [map1 length]. Instead of making a substring with a particular range, it would be best to just use [map1 characterAt:i] and subtract '0' to that value to convert from ascii to an actual number.
Quote this message in a reply
Member
Posts: 29
Joined: 2006.07
Post: #3
Thanks a lot for the advice. I'm still stuck on a few things. Sorry if these are total newbie questions, but I'm getting the hang of it.

Using nextTile = [mapl characterAt:i]; generates a warning that NSString may not respond to characterAt: and brings up an error and the debugger when I run the program. I tried using the existing substring method and subtracting 0 from the resulting value but again, error.

As far as loading my images and variables goes, should they be loaded once in a seperate routine and then released when the application terminates? And for mapl, should I use a custom initializer to declare the string's value at initialization? What about nextTile that constantly changes?

On the bright side, [mapl length] did work. I'm coming from a background in BASIC and Visual Basic, so memory management is a tough subject for me, but it's starting to make more sense. If anyone wants me to upload the XCode project to take a look at it, let me know. I'd like to try and learn as much as I can on my own though : )
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #4
The method you're looking for is characterAtIndex:
Give that a go.
The API docs will be a lifesaver - for example, if you want the documentation for NSString you can option-double-click on NSString in XCode and it will look up the class for you.

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #5
Oops, sorry, yeah, I got that name wrong. Rasp I got that from the docs, but I guess I forgot to put in the last part... :bllush:

Anyway, the docs are very helpful indeed. The 2 main pages you will need to look at are this and this.

To answer your other question, tile01 and 02 should be stored as class member variables (declared between the curly braces in your .h file) and should be initialized in your initWithFrame method. You should also declare your own dealloc method, where you release those variables.

For map1, simply saying "NSString *map1 = @"...";" is sufficient: that's a literal constant that will be stored in the executable itself. It's automatically created on launch and destroyed (in memory) at exit. You might also want to look into using regular C strings for things such as this, though. Since they're really just arrays, you can do anything with them that you could with arrays. For example, instead of
NSString *map1 = @"...";
you would have
const char *map1 = "...";
Then, instead of doing
nextTileNum = [map1 characterAtIndex:i] - '0';
you would simply do
nextTileNum = map1[i] - '0';

If anything, you would at least get less overhead. I also like C strings since you can do whatever you like with them, including editing the contents, assuming you have enough space allocated. Be careful, though: if you use a literal string (with "") it's put in protected memory. You need to use something like strcpy to copy the string, or strdup to duplicate it. (the resulting string with strdup is in allocated memory, though, so you'll need to free it; strcpy, however, can be placed in pre-allocated memory, such as a simple static array that doesn't need to be freed)

I guess this last part leads to my next suggestion: you should probably learn C. It will teach you a lot of what you need to know with the lower-level portions of ObjectiveC, and also it can teach you a lot about memory management without getting into all the different conventions and rules with ObjectiveC objects. (though you can usually bend ObjectiveC to your will and use a sort of C-style memory management if you know what you're doing; I tend to do that myself: I never retain an object and I set it so I only need to call release once)
Quote this message in a reply
Member
Posts: 257
Joined: 2004.06
Post: #6
vnvrymdreglage Wrote:However, when I run it, everything is covered in tree (2) tiles, instead of being grass, tree, grass, tree (1212) like in the string. Any ideas of what is wrong? I think my switch statement is executing all of the code for some reason instead of just executing the corresponding case.

Um, I don't think anyone's pointed this out yet but your case statements need to be terminated with a break statement like so:

Code:
switch( someNumber )
{
case 0:
    do stuff;
    break;

case 1:
    do other stuff;
    break;

default:
    // some compilers complain if you don't have a default case
    do default stuff;
    break;
}

Otherwise, when you jump to one case, it'll just fall through to the cases after it.

The brains and fingers behind Malarkey Software (plus caretaker of the world's two brattiest felines).
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #7
I'd recommend against mixing C strings and NSStrings unless you have a very good reason to. Keeping track of which is which is likely to be more trouble than it's worth, especially if you don't have your mind wrapped around things like memory management and stuff yet. Use NSString when you want a constant string, and NSMutableString when you need to use its extra functionality. It will save you a lot of headaches. Know the rules before you break them Grin

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
Member
Posts: 254
Joined: 2005.10
Post: #8
Malarkey Wrote:Um, I don't think anyone's pointed this out yet but your case statements need to be terminated with a break statement like so:

Code:
switch( someNumber )
{
case 0:
    do stuff;
    break;

case 1:
    do other stuff;
    break;

default:
    // some compilers complain if you don't have a default case
    do default stuff;
    break;
}

Otherwise, when you jump to one case, it'll just fall through to the cases after it.

Indeed, if you don't put the break at the end of case 1, then you fall through to case 2 which draws your tree over your grass. Additionally, both draw 1 and draw 2 are put into the log for the same reason, if you look at the log output you will notice double the number you should be expecting. Edit: Actually you have about 20 log outputs which is > 13 and less than 26 since you draw only tile 2 sometimes.
Quote this message in a reply
Member
Posts: 29
Joined: 2006.07
Post: #9
Awesome, the break; statements fixed everything. I'll try some of the other suggestions you all gave me as well, but at least it's working right now. Thanks everyone! I'll post back later if I have any more issues.

Some changes I'd like to make :
- fix the memory leaks
- support a whole screen full of tiles ( I guess using x,y would be the best way? )
- support up to 9 different tiles (1 through 9)
- use an array of NSImage objects for the tiles instead of a large switch statement

I'm already building a nifty tile-based RPG engine in Visual Basic and I'm trying to carry over some of the similar features and methods I used.

Also, using characterAtIndex:i causes the program to crash for some reason. It gives me OpenEdit has exited due to signal 10 (SIGBUS).
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #10
Are you sure you're using characterAtIndex with map1 rather than nextTile? (which is no longer needed) Also, you should use indexing with 0 to 9 instead of 1 to 9; you can also use an array of images instead of the switch statement with that system.
Quote this message in a reply
Member
Posts: 29
Joined: 2006.07
Post: #11
akb825 Wrote:Are you sure you're using characterAtIndex with map1 rather than nextTile? (which is no longer needed) Also, you should use indexing with 0 to 9 instead of 1 to 9; you can also use an array of images instead of the switch statement with that system.

Yes, I had nextTile = [mapl characterAtIndex:i]; I also tried characterAtIndex:2 to make sure it wasn't just a problem with 'i'. It still gives me an error. As for not needing nextTile anymore after it's converted, should I release the variable? What happens during the next loop cycle when it needs to use it again?
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #12
Can you show me all the code that has to do with map1? It's likely you're doing something that destroys the string that you put in there.
Quote this message in a reply
Member
Posts: 29
Joined: 2006.07
Post: #13
I could upload the XCode project if you like. And by the way, it's mapL, not map1. I know it looks like a 1 on here though : ) And yet another question : to put my NSImages into an array, do I need to create an NSArray object?

Code:
#import "MyView.h"

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
    }
    return self;
}

- (IBAction)changeColor:(id)sender
{
}

- (IBAction)eraseView:(id)sender
{
}

- (IBAction)redrawView:(id)sender
{
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{    
    NSImage *tile01 = [[NSImage imageNamed:@"T01"] retain];
    NSImage *tile02 = [[NSImage imageNamed:@"T02"] retain];
    
    NSString  *nextTile    = [[NSString alloc] init];
    NSString  *mapl        = [[NSString alloc] init];
               mapl        = @"1212121212121";
               int           nextTileNum;

    NSRect bounds = [self bounds];

    [[NSColor blackColor] set];
    [NSBezierPath fillRect:bounds];

    NSPoint tilePoint;
    tilePoint.x = (0);
    tilePoint.y = (160-16);
    
    int i;
    
    for ( i = 0; i < [mapl length]; i++ ) {
        nextTile = [mapl substringWithRange:NSMakeRange(i,1)];
        nextTileNum = [nextTile intValue];
        switch (nextTileNum) {
            case 1:
                tilePoint.x = (i * 16);
                tilePoint.y = (144);
                [tile01 dissolveToPoint:tilePoint fraction:1.0];
                NSLog(@"Drew Tile 1.");
                break;
            case 2:
                tilePoint.x = (i * 16);
                tilePoint.y = (144);
                [tile02 dissolveToPoint:tilePoint fraction:1.0];
                NSLog(@"Drew Tile 2.");
                break;
        }
        
    }
    
    [tile01 release];
    [tile02 release];
}
@end
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #14
You took out the characterAtIndex: call, so I can't really debug that problem. However, one thing that I will say, is you need to take out both your [[NSString alloc] init] calls. That memory is never being used, since you then assign mapl and nextTile to different values afterwards, that memory you allocated is never touched.

To put the values in an array, you don't need to use an NSArray. A regular array will do fine. Between the curly braces in your .h file, declare this:
NSImage *tiles[2];
If you decide later to have 10 tiles, replace the 2 with a 10. Then, in the initWithFrame method, you should set tile01 into index 0 and tile02 into index 1. Then, change mapl to alternate between 0 and 1, and just us the index that you grab from the string. Create a dealloc method, then, and release all the objects in the array.

If you have further problems, posting the project would help.
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2006.10
Post: #15
I will echo akb825's statements that you don't really need to allocate memory for either nextTile or mapl since these both get assigned to temporary strings created elsewhere. In fact you could just say
Code:
NSString *mapl = @"1212121212121";

The reason why you were having trouble with characterAtIndex: is because you were trying to assign its return value to an NSString variable. characterAtIndex: returns a unichar, which is essentially an unsigned short int. So something you could do instead is
Code:
unichar nextTile = [mapl characterAtIndex:i];
int nextTileNum = nextTile - '0';

Another handy Cocoa trick is
Code:
NSPoint tilePoint = NSMakePoint( 0, 160-16);
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  C: Global Variables versus Parameters Lizard Man 10 6,632 Jan 13, 2010 08:22 PM
Last Post: Lizard Man
  Accessing an inherited class's variables Tobs_ 22 10,333 Feb 28, 2007 05:26 PM
Last Post: mac_girl
  Should global variables be pointers or full objects? ia3n_g 1 2,543 Aug 4, 2006 05:53 PM
Last Post: OneSadCookie
  where do global variables fall into the memory type? WhatMeWorry 3 3,008 Jun 5, 2006 02:45 PM
Last Post: OneSadCookie
  Arrays or variables containing executable functions Jones 4 4,967 Jun 2, 2006 08:35 AM
Last Post: Zekaric