Objective-C newbie: Copying object to object

Discussion in 'Mac Programming' started by NickFalk, Aug 12, 2008.

  1. NickFalk macrumors 6502

    NickFalk

    Joined:
    Jun 9, 2004
    #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.
     
  2. killerwhack macrumors regular

    killerwhack

    Joined:
    Aug 5, 2004
    Location:
    Los Angeles, California
    #2
    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.
     
  3. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #3
    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.
     
  4. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #4
  5. kpua macrumors 6502

    Joined:
    Jul 25, 2006
    #5
    FYI, the correct way to implement copying is to declare conformance to the <NSCopying> protocol and then implement its only method: -copyWithZone:.
     
  6. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #6
    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
     
  7. NickFalk thread starter macrumors 6502

    NickFalk

    Joined:
    Jun 9, 2004
    #7
    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! :)
     
  8. zmttoxics macrumors 65816

    zmttoxics

    Joined:
    May 20, 2008
    #8
    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?
     
  9. lazydog macrumors 6502a

    Joined:
    Sep 3, 2005
    Location:
    Cramlington, UK
    #9
    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
     
  10. zmttoxics macrumors 65816

    zmttoxics

    Joined:
    May 20, 2008
    #10
    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.
     
  11. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #11
    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 *.
     
  12. zmttoxics macrumors 65816

    zmttoxics

    Joined:
    May 20, 2008
    #12
    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.
     

Share This Page