7
@interface some_class : some_parent {

 }
@property (nonatomic, assign) CGRect the_rect;

@end

...

@implementation some_class

@synthesize the_rect;

@end

After creating an instance of some_class:

instance_of_some_class.the_rect.origin.x = 0.0;

Which generates an "Expression not assignable" error.

Of course this works fine:

instance_of_some_class.the_rect = CGRectMake(0,0,0,0);

I guess the problem is that the auto-magically created setter only knows about assigning a CGRect to "the_rect". OK, I get that. Can I tell it to not create a setter so that I can access the member variable directly and make assignments to the struct members without having to assign the entire CGRect?

I suppose that I could break this out into four individual CGFloats for origin.x, origin.y, size.width and size.height...but you'd think there would be a way to do this with CGRects instead.

What's confusing is that this, of course, works fine:

CGRect test = instance_of_some_class.the_rect.origin.x;

In other words, the getter knows how to navigate the struct and pull out the value of one of its elements so I can use it. The opposite does not seem to be the case as you just can't reach into an ivar that happens to be a struct and make an assignment to one of its elements without having to assign the entire struct.

I also tried this:

@interface some_class : some_parent {

@public

    CGRect the_rect;

 }
@property (nonatomic, assign) CGRect the_rect;

@end

After instantiating in another module:

instance.the_rect->origin.x = some_value;

...and got an error saying that the member being referenced is not a pointer. I tried taking the address of instance.the_rect ... but that didn't work either.

EDIT TO CLARIFY: This is about creating a class that has some ivars that happen to be structs. Then, after instantiating the class somewhere else, you want to be able to make assignments to the struct ivar elements directly:

class_instance.struct_ivar.element = something;

I am using CGRect as a convenient example of a well-known struct that one might want to use as an ivar.

martin's
  • 3,853
  • 6
  • 32
  • 58

4 Answers4

12

So you know how to get around your problem, but here's why you have to do so and one of the reasons why dot syntax in Objective-C is an unholy perversion:

anInstance.property = blah is compiled to the exact same code that would be generated if you had instead typed: [anInstance setProperty:blah]

This works fine if your properties are only object properties. Unfortunately for us, dot syntax already has an unrelated meaning in C, and that's to access members of a struct. ie, you can do:

NSRect foo;
foo.origin.x = 42;

If foo were an object, then foo.origin.x = 42 would be the same as [[foo origin] setX:42] (assuming origin were also an object). However, since they're simply structs, it's just doing an offset calculation and setting the value in memory directly. There's no method invocation going on.

Enter dot syntax in Objective-C. Now we have two different meanings of the dot operator (something that not even C++ allows, ironically enough, with all of its operator overloading). Sometimes you're using it to access a struct member. Sometimes you're using it to invoke an object accessor. Sometimes you can mix-and-match those uses. Sometimes you can't.

You can mix-and-match member vs accessor access if the expression is being used as an rvalue. In other words, you can do:

foo = anObject.frame.origin.x;

However, if you try to do this as an lvalue, it fails:

anObject.frame.origin.x = foo;

The way to work around this is to do:

NSRect frame = anObject.frame;
frame.origin.x = 42;
anObject.frame = frame;

Of course, if you just forgot that dot syntax existed altogether, then you'd never have this problem, because trying to coming up with a way to do this using bracket syntax would be non-sensical.

And this is one of the many reason why I think dot syntax was a terrible mistake. You never would've been confused about this if it had never been added.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • thanks for your explanation but I've a question: what is the exactly reason that we can't use `anObject.frame.origin.x = foo`? Because the `anObject.frame` returns a copy of the struct and not the real struct? (in that case all it's clear because modifying a value in a copy struct is non-sensical) – Fred Collins Feb 21 '12 at 05:47
6

You don't generally access member variables directly from outside the class. You can declare it as @public if you really want to. I think you may have to create accessors for each of the rect's members:

- (void)setTheRectX:(CGFloat)newX {
    the_rect.origin.x = newX;
}

- (void)setTheRectY:(CGFloat)newY {
    the_rect.origin.y = newY;
}

- (void)setTheRectOrigin:(CGPoint)newOrigin {
    the_rect.origin = newOrigin;
}

and so on.

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.

There are a few questions floating around SO that discuss this issue: 1 2 3

Public ivar:

@interface RectHolder : NSObject {

@public
    CGRect the_rect;


}

Access:

RectHolder * myRH = [[RectHolder alloc] init];
myRH->the_rect = CGRectMake(0.0, 0.0, 100, 100);

myRH->the_rect.origin.x = 10;

NSLog(@"%@", NSStringFromRect(myRH->the_rect));
Community
  • 1
  • 1
jscs
  • 63,694
  • 13
  • 151
  • 195
  • Yes, need to access CGRect elements from outside the class (in other words, an instance) and make assignments. I can assign to the entire CGRect just fine all day long. The problem is if I want to do something like: the_rect.origin.x = some_value; – martin's May 02 '11 at 18:34
  • Right, I could even go as far as breaking apart the struct into individual ivars and then the setter/getters are auto-magic. This becomes less practical as the struct gets more complex. You could have a struct with members that are structs as well. As an example, imagine a struct that might have four CGRect structs as its elements. You don't really want to have to dismember the struct into 16 different ivars in order to have assign access at the sub-element level. And, at the same time, you don't want to have to write 16 individual setters...but I almost don't see a way out. – martin's May 02 '11 at 19:03
  • Maybe the way out is to convert structs into classes. So, you'd create a CGRect-equivalent class and then use that as the ivar type for a class that needs CGRect ivars. Maybe then you can access the elements directly...but this is most definitely ugly. – martin's May 02 '11 at 19:03
  • That's pretty much what classes/objects are for, as far as I know. You might be able to do a pointer to the struct as your ivar, but then you have to fuss around with the memory. – jscs May 02 '11 at 19:05
  • You _can_ just declare it `@public` and move on. Then access it with `->` syntax. – jscs May 02 '11 at 19:06
  • I tried that before posting and didn't work. Maybe I got it wrong. Rather than try to document here I am editing my question to reflect this in more detail. – martin's May 02 '11 at 19:28
  • @Martin: you need to declare the _ivar_ public, not the property. See my updated answer. – jscs May 02 '11 at 19:30
  • Ah! Objective-C strikes again! I need to remember that the dot notation is shorthand for [some_class some_method]. If I do I won't get wrapped around a post like this! Thanks. I am going to go with the @public approach and pointer access. It satisfies my need for something much cleaner than writing a million little custom setters. – martin's May 02 '11 at 19:56
  • @Martin the easy way to remember that `someObject.foo = bar` is the same as `[someObject setFoo:bar]` is simple: never use dot syntax unless you're working with structs. Then you'll *never* have this problem. – Dave DeLong May 02 '11 at 20:24
  • @Dave: are you in the [BNR anti-dot-syntax](http://weblog.bignerdranch.com/?p=83) camp, then? – jscs May 02 '11 at 20:28
  • @Josh Caswell: absolutely. Adding dot syntax to the language added nothing of value. It has only served to increase confusion. – Dave DeLong May 02 '11 at 20:30
  • @Dave: Nah. Doesn't bother me. It's just a matter of understanding the language better. I program in about twenty different languages. It is only natural that there would be some "mental crosstalk" as you approach a new language to muck things up. In my experience it takes from six to twelve months of usage for a language to feel like you own it. I've been on Objective-C for about five months. Now I am trying to see if I can answer one question per day on SO, which is a great way to learn even more. Not worried about dot syntax at all. – martin's May 02 '11 at 21:06
  • @Martin: I agree. I am constantly rewarded by the things I learn when answering, or just _trying_ to answer, questions for other people. It's pretty awesome – jscs May 02 '11 at 21:08
  • @John Caswell: Well, that's the way we did it back when USENET was the resource. I learned a ton by helping others out on USENET. StackOverflow is definitely where it's at today. – martin's May 02 '11 at 21:21
1

You can't assign to elements of a struct. This is what you must do instead:

CGRect rect = object.rect;
rect.origin.x = 3.0f;
rect.size.height = 53.3f;
object.rect = rect;

By doing this you aren't assigning to the rect's elements, but rather changing the entire rect value instead.

Alexsander Akers
  • 15,967
  • 12
  • 58
  • 83
  • Yes, I know that. This is exactly what I am trying to avoid. It makes an unnecessary mess out of what could be otherwise simple code. I have a class with six CGRects and eight CGPoints. Lots of intensive math on the class instance. I have to create a pile of intermediate variables in order to do the math and make the assignments. There's also an issue of performance. If all I want to do is update, say, ..origin.x and I am forced to create an intermediate variable, read the property into it, then do the math and then update the entire CGRect, that burns clock cycles I may not have. – martin's May 02 '11 at 19:07
  • This is the only way to do it as allowed by Objective-C 2.0. – Alexsander Akers May 02 '11 at 19:10
0

This is if you want a static class variable, which I know now is not what the OP wanted.


If you want a class variable, I don't think that's how you do it. I just tested, and the value is not sustained between elements. Not sure why it's letting you synthesize empty setters/getters though without a warning.

For a method to create class variables, I think the best bet is to use the static keyword, much like in C. i.e.

static CGRect the_rect; // in .h file outside the @implementation

And then initialize it in + (void)initalize. You probably have to write the setters and getters yourself though.

Gustav Larsson
  • 8,199
  • 3
  • 31
  • 51
  • I don't think this has anything to do with my question. The issue I am presenting has to do with being able to directly assign values to the elements of an ivar that happens to be a structure. This, of course, once the class in question is instantiated somewhere else --NOT from inside the class. – martin's May 02 '11 at 18:50
  • I thought you wanted a class variable and not an ivar, got it. In that case, I think all you're missing is a declaration in the `@interface` block, because I use such CGRects all the time in that way, and I can assign `rect.origin.x` directly. – Gustav Larsson May 02 '11 at 19:18
  • Can you post some code? Everything I have tried and what I have researched in SO indicates that this is simply not possible. – martin's May 02 '11 at 20:02
  • Scratch that, I was wrong. I use them so much, I guess I always thought you could. I'm surprised I've never stumbled upon this before. Good to know! – Gustav Larsson May 02 '11 at 20:22