Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

NickFalk

macrumors 6502
Original poster
Jun 9, 2004
347
1
Hi there. I've been spending the last week or so with my first baby-steps in Objective-C. I'm not a complete novice to programming, but haven't programmed any serious stuff for almost 20 years (wow, how time flies!)

I'm quite new to the whole Object-oriented side of things and while I've seen the light when it comes to the usefulness though, but struggle with the following:

I've coded a small RPG-style fight between two Objects of the class "Robot". The actual fight is handled inside the object in a fight-method. This method receives a temp-copy of the opposing Robot-object.

Everything seemed to work fine and I decided to create a few "standard" Robots with different variabels for strength, armour, life-force etc.

(Not code, just example:)
robot1: strength= 100 armour=50 life-force=100
robot2: strength= 50 armour=100 life-force=100

Before initiating the fights my code simply copied each of these objects into my defined objects of the type Robot:

Code:
computerRobot = robot1;
playerRobot = robot2;

The above works fine as does the fight-method. However, when trying to initiate a fight between two robots of the same type things go awry.

Code:
computerRobot = robot1;
playerRobot = robot1;

While my understanding was that the above would simply copy the content from robot1 into the respective robots it seems both computerRobot and playerRobot actually becomes*robot1. (This is probably obvious to most of you, but not me).

So, when the computerRobot damages the playerRobot it receives the same damage itself (as does the original robot1 object).

----

Hopefully this make some sense and hopefully some of you geniuses will be able to inform me what basic concept I've missed along the way.
 

killerwhack

macrumors regular
Aug 5, 2004
237
1
Los Angeles, California
The "=" method

When you write:


robota = robot1;

You need to write a copy method. Something that allocates new storage and then initializes the new storage with the object contents being copied.
 

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
You could write something like:
Code:
(Robot *) initWithRobot:(Robot *) originalRobot {
  armor = [originalRobot getArmor];
  life = [originalRobot getLife];
  strength = [originalRobot getStrength];
  return self;
}
and use it like:
Code:
Robot *robotA = [[Robot alloc] initWithStrength:100 andLife:50 andArmor: 50];
Robot *playerRobot = [[Robot alloc] initWithRobot:robotA];
Robot *computerRobot = [[Robot alloc] initWithRobot:robotA];

You could also do something like -copy that instantiates a new Robot with a copy of the current robots Strength, Life, and Armor.

Code:
(Robot *) copy {
  return [[Robot alloc] initWithStrength:strength andLife:life andArmor: armor];
}

and use it like:
Code:
Robot *robotA = [[Robot alloc] initWithStrength:100 andLife:50 andArmor: 50];
Robot *playerRobot = [robotA copy];
Robot *computerRobot = [robotA copy];

The problem you were having was in assigning a pointer. I'm not sure how familiar you are with C (though it was certainly alive and kicking in '88), but essentially a pointer is a variable that holds the address to another variable (or in this case, instance of a class). You took the pointer to your original object, and assigned it's value (a memory address) to two other pointers. At this point all three of these pointers contain the same memory address, so they reference the same Object. I'm not sure if that clarifies the issue or further obscures it, but hopefully the former.

-Lee

Edit: In the case of the copy method, you might want to assign to a Robot *, retain it, then return the Robot *. This is more of a convention, as init methods tend to return things that are not retained and copy methods return things that are. Someone more adept at Obj-C might be able to clarify this further.
 

kpua

macrumors 6502
Jul 25, 2006
294
0
FYI, the correct way to implement copying is to declare conformance to the <NSCopying> protocol and then implement its only method: -copyWithZone:.
 

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
Hm, learn something new every day. I hadn't played with protocols before and decided to give this a whirl. I used @property in this example, but when I actually did this on my machine I had to manually declare accessors and mutators b/c i'm not on leopard yet. As such, i can't guarantee that the @property stuff is right/works to synthesize the accessors and mutators, but i think it's correct. How does this look?

Robot.h:
Code:
#import <Cocoa/Cocoa.h>

@interface Robot : NSObject <NSCopying>{
  int Strength;
  int Life;
  int Armor;
}
- (id)copyWithZone:(NSZone *) zone;
- (Robot *)initWithRobot:(Robot *) copyFrom;
- (Robot *)initWithStrength:(int) inStrength andLife:(int) inLife andArmor: (int) inArmor;
- (void)battleRobot:(Robot *) opponent;

@property (assign) int Strength;
@property (assign) int Life;
@property (assign) int Armor;

@end

Robot.m:
Code:
#import "Robot.h"
@implementation Robot

- (id) copyWithZone:(NSZone *)zone {
  Robot *copy = [[[self class] allocWithZone:zone] initWithRobot:self];
  return copy;
}

- (Robot *) initWithStrength:(int) inStrength andLife: (int) inLife andArmor: (int) inArmor {
  Strength=inStrength;
  Life=inLife;
  Armor=inArmor;
  [self retain];
  return self;
}

- (Robot *) initWithRobot:(Robot *) copyFrom {
  Strength=[copyFrom getStrength];
  Life=[copyFrom getLife];
  Armor=[copyFrom getArmor];
  [self retain];
  return self;
}

- (void) battleRobot:(Robot *)opponent {
  int opponentArmor = [opponent getArmor];
  opponentArmor -= Strength;
  if(opponentArmor < 0) {
    [opponent setLife:([opponent getLife] + opponentArmor)];
    [opponent setArmor:0];
  } else {
    [opponent setArmor:opponentArmor];
  }

  if([opponent getLife] > 0) {
    Armor -= [opponent getStrength];
    if(Armor < 0) {
      Life += Armor;
      Armor = 0;
    }
  }
}
@end

main.m:
Code:
#import "Robot.h"

int main(int argc, const char *argv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  Robot *robotTemplate = [[Robot alloc] initWithStrength:25 andLife:131 andArmor: 70];
  Robot *playerA = [robotTemplate copy];
  Robot *playerB = [robotTemplate copy];
  [robotTemplate release];

  BOOL AsTurn = true;
  while([playerA getLife] > 0 && [playerB getLife] > 0) {
    if(AsTurn) {
      [playerA battleRobot:playerB];
    } else {
      [playerB battleRobot:playerA];  
    }
    AsTurn=!AsTurn;
    NSLog(@"Current stats:\nplayerA has %d armor and %d life.\nplayerB has %d armor and %d life.\n%@ goes next!\n",[playerA getArmor],[playerA getLife],[playerB getArmor],[playerB getLife],AsTurn ? @"playerA" : @"playerB");
  }

  NSLog(@"%@ wins!",[playerA getLife] > 0 ? @"playerA" : @"playerB"); 
  [playerA release];
  [playerB release];
  [pool release];
  return 0;
}

-Lee
 

NickFalk

macrumors 6502
Original poster
Jun 9, 2004
347
1
Thanks a lot guys. It makes perfect sense, and yes I have some knowledge of pointers so I do get that. (Even though I'm not used to think in those terms). It'll probably come back to me over time though. ;)

I was actually thinking last night to make those "standard" robots into subclasses of Robot. It could make sense as I would probably use the same kind as a basis several times over. Guess I have to put my thinking cap on for this one. Thanks a lot guys! :)
 

zmttoxics

macrumors 65816
May 20, 2008
1,020
1
Not to thread jack or anything but I was going to recommend the easy C way of memcopy - thought I should try it out first to be sure and I couldnt get the size part to work for NSString objects.

This succeeds (returns the object instead of null which is a pass):
Code:
NSString src = [[NSString alloc] initWithFormat:"String!"]; //i think thats ok, this is on the fly
NSString dest =[[NSString alloc] init];
memcpy(dest, src, sizeof(src));

However, the destination never receives the object unless I hard code the size. If I put 7 there or 256 (a valid int), it will work. But if that int is a variable, it will fail again.

Code:
/* This works */
memcpy(dest, src, 256);

/*This doesn't */
int size = [src length];
memcpy(dest, src, size);

Why is that? Is this part of the Objective C implementation? Or is it that C functions cant understand Objective C objects?
 

lazydog

macrumors 6502a
Sep 3, 2005
709
6
Cramlington, UK
This succeeds (returns the object instead of null which is a pass):
Code:
NSString src = [[NSString alloc] initWithFormat:"String!"]; //i think thats ok, this is on the fly
NSString dest =[[NSString alloc] init];
memcpy(dest, src, sizeof(src));

memcpy(dest, src, sizeof(src)); is going to copy 4 bytes because src is a pointer and so sizeof returns the size of a pointer.

Anyway, you're dealing with objects, not c strings so your code is copying the first few bytes, (or far too many bytes) of one instance of an NSString object over another instance. Crazy things are bound to happen as a result!

b e n
 

zmttoxics

macrumors 65816
May 20, 2008
1,020
1
memcpy(dest, src, sizeof(src)); is going to copy 4 bytes because src is a pointer and so sizeof returns the size of a pointer.

Anyway, you're dealing with objects, not c strings so your code is copying the first few bytes, (or far too many bytes) of one instance of an NSString object over another instance. Crazy things are bound to happen as a result!

b e n

Whoops! Ya, I know its a PTR, but its still odd that providing an int variable with say 7 as a value fails but while providing 7 the integer it works.

I think I will give up and just keep C out of my ObjC code. I was hoping they would intertwine better - but apparently not.
 

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
Not to thread jack or anything but I was going to recommend the easy C way of memcopy - thought I should try it out first to be sure and I couldnt get the size part to work for NSString objects.

This succeeds (returns the object instead of null which is a pass):
Code:
NSString src = [[NSString alloc] initWithFormat:"String!"]; //i think thats ok, this is on the fly
NSString dest =[[NSString alloc] init];
memcpy(dest, src, sizeof(src));

However, the destination never receives the object unless I hard code the size. If I put 7 there or 256 (a valid int), it will work. But if that int is a variable, it will fail again.

Code:
/* This works */
memcpy(dest, src, 256);

/*This doesn't */
int size = [src length];
memcpy(dest, src, size);

Why is that? Is this part of the Objective C implementation? Or is it that C functions cant understand Objective C objects?

Never try to do this with Objects. First off, you can't have a local copy of a class, only pointers. Secondly, the length method of NSString returns the number of characters, not the size in bytes, of that string. Even if you could get the right length of an NSString object, once you'd copied it, who knows what would actually be in there. There are probably pointers to some other storage that is the backing store for the Objects data, etc. What happens when the original is released? Those items the new Object points to will be freed, and you'd have no idea. Sending an extra retain won't help any more, because you now have a "new" object. What if the original object is mutable? Does the new copy have the changes, or not? There's no way to tell.

The moral is that even if you could find a way to do this, you never should. The runtime does all sorts of things you don't know about. I can't say why memcpy was failing with a variable, but I'm honestly more surprised that something appeared to work no matter what you passed to memcpy.

It's an interesting idea, I don't want to be rude at all, but there's too much going on with the runtime to pull this off as far as I know.

-Lee

Edit: Oops, too slow. lazydog got to this first, and more succinctly. I wasn't clear that a pointer would be getting copied, since you didn't declare src and dst as NSString *.
 

zmttoxics

macrumors 65816
May 20, 2008
1,020
1
Well, strickly speaking it shouldn't matter as long as you use the size of the object when doing the copy (sizeof(object) is usually the case). Problem is I can't get the correct size of the NSString objects.

Oh well, it doesn't matter as I have given up on that idea for now.

PS: I know that code I posted is wrong (i just read it now, lol), it was an early morning today.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.