6

I have class A with a header that looks something like this:

typedef struct {
    int x;
    int y;
} Position;

@interface ClassA : NSObject

@property Position currentPosition;

@end

And I try to assign individual values of the position struct from the property in another class like this:

ClassA * classA = [ClassA new];
classA.currentPosition.x = 10;

Which gives an error "expression is not assignable" and won't compile.

I can set it like this:

ClassA * classA = [ClassA new];
Position position = {
    .x = 1,
    .y = 2
};

classA.currentPosition = position;

And I can even alter individual "properties" of position variable like this:

ClassA * classA = [ClassA new];
Position position = {
    .x = 1,
    .y = 2
};

// WORKS
position.x = 4;    

// DOESN'T WORK
// classA.currentPosition.x = 4;

classA.currentPosition = position;

Why can't I set values individually when they are a property?

Logan
  • 52,262
  • 20
  • 99
  • 128
  • If you used typedef, you don't need to add 'struct' in the @property declaration. – Nicolas Miari Apr 24 '14 at 23:50
  • @NicolasMiari - I corrected it, I have been trying different ways of declaring, I just copied over the wrong stuff. – Logan Apr 24 '14 at 23:52
  • See also [How to gain assignment access to CGRect elements when the CGRect is an instance?](http://stackoverflow.com/q/5860755), even though it's pre-declared-property. – jscs Apr 25 '14 at 00:43
  • @JoshCaswell - I didn't think those really explained "why" I'm able to set individual values as a variable but not as a property. I can't really remove the question now that people have answered, but I'll try and be more thorough in the future. – Logan Apr 25 '14 at 00:47
  • [grahamparks's answer](http://stackoverflow.com/a/4360343/603977) "The reason you can't do this: `self.boundingBox.origin.x = minX;` Is because there's no way to call `setBoundingBox:` but only ask it to change `origin.x`" doesn't answer why?! [My answer](http://stackoverflow.com/a/5860846/) "The issue is that when you ask for the instance's `the_rect`, you don't get a pointer to the same rect that the instance has, like you would if the ivar was an object pointer -- you get a new copy of the struct." doesn't explain why?! – jscs Apr 25 '14 at 00:49
  • @JoshCaswell - My bad, I try and catch em, I just missed it. I'll watch out in the future. – Logan Apr 25 '14 at 00:51
  • Don't worry about it; my duplicate proposal and other link aren't meant as criticism. I hope you don't take them that way. – jscs Apr 25 '14 at 00:52

2 Answers2

6

This expression:

classA.currentPosition

returns a temporary copy of your struct, not the struct itself. The compiler error is telling you that you can't assign a value to some member of that temporary copy (because it's an rvalue, technically). But you don't want to assign a value to that member anyway, because it would just disappear along with the struct itself.

So why are you only getting a copy of the struct in the first place?

Because

@property Position currentPosition

is actually just shorthand for:

-(Position)currentPosition;
-(void)setCurrentPosition(Position value);

and in C-family languages, the first line (the getter) indicates that it's returning a Position struct by-value, or as a copy.

You could make your own accessor that returns a reference, but you probably shouldn't. This isn't a common idiom in Objective-C -- at least not in this context -- and you should generally try to stick with common idioms for a language.

Instead, you should use position like the following;

Position pos = classA.position;
pos.x = 4;
classA.position = pos;

Lastly, if you really want to be able to set currentPosition using the syntax you originally desired, while maintaing Objective-C idioms, you could just make Position a class rather than a struct. Then, the property can return a Position * and the rest of the syntax would work. Make sure to initialize the pointer in your init function (or when appropriate).

adpalumbo
  • 3,031
  • 12
  • 12
  • 1
    This seems to answer my question, but I'm curious why it got a downvote? Is there something that makes this incorrect? – Logan Apr 25 '14 at 00:48
  • You need 3 lines every time you want to assign one of the properties in the struct. It clones your struct every time you access it. So you're not actually assigning properties, you're just constantly creating new instances of that struct with different values. – manecosta Apr 25 '14 at 01:02
2

Properties don't work for C structs.

You can do it like:

@property Position *currentPosition;

Basically, using a pointer.

Now you actually need to initialize that pointer so:

- (id) init {
    self = [super init];
    if(self){
        self.currentPosition = malloc(sizeof(Position));
    }
    return self;
}

Then, don't forget to use arrow notation, since you're dealing with a pointer:

classA.currentPosition->x = 5;

And don't forget to free the memory you requested!

-(void)dealloc{
    free(self.currentPosition);
}
manecosta
  • 8,682
  • 3
  • 26
  • 39
  • 1
    You can do this (it is valid), but you shouldn't (it is *not* idiomatic). There are better and more Objective-C-ish ways to do the same thing. – adpalumbo Apr 25 '14 at 00:44
  • This gives an error on `self.currentPosition = malloc(sizeof(Position));` - "Assigning to 'Position' from incompatible type 'void*'" – Logan Apr 25 '14 at 00:49
  • 2
    That error means you didn't change the property declaration to be a pointer, @Logan. – jscs Apr 25 '14 at 00:51
  • What the hell people.. 2 downvotes already on a perfectly valid solution. I didn't say it was the best way, I said "You can do it like:" – manecosta Apr 25 '14 at 00:54
  • Anyway, I'm glad it helped you some way! – manecosta Apr 25 '14 at 00:55
  • Ya, thanks for answering, it's definitely an interesting approach. I wasn't able to get it working, because it was throwing other errors that I don't have time to fix right now. I'll try again tomorrow. -1 isn't mine, not sure why people are downvoting. – Logan Apr 25 '14 at 01:25