views:

341

answers:

4

Say I have the following:

@interface MyClass : NSObject { NSString* _foobar; }
@property (nonatomic, retain) NSString* foobar;
@end

@implementation MyClass
@dynamic foobar;
- (void) setFoobar:(NSString*)fbSet; { [_foobar release]; _foobar = [fbSet retain]; }
- (NSString*) foobar; { return _foobar; }
@end

Then:

MyClass* mcInst = [[[MyClass alloc] init] autorelease];
NSLog(@"I set 'foobar' to '%@'", (mcInst.foobar = @"BAZ!"));

Looking at the return value of -[MyClass setFoobar:], one might assume here that this line would print I set 'foobar' to '', because the assignment appears to return nothing.

However - thankfully - this assignment acts as expected, and the code prints I set 'foobar' to 'BAZ!'. Unfortunately, this feels like a contradiction, because the invoked setter's return value belies the fact that the assignment returns the value assigned to it. At first I figured that mcInst.foobar = @"BAZ!"; is making two calls instead a block: first the setter and then the getter to gather the return value. However, instrumenting the setter and getter methods with NSLog calls proves this isn't the case.

I'd like to understand the ambiguity here. Thanks!

A: 

in C, assignment is an expression, which evaluates to the assigned value

newacct
I understand, and even pointed that out. I'm looking for the details of Objective-C's implementation.
rpj
+5  A: 

Quick Summary:

The quick answer here is that there is no contradiction, because the result of the expression:

(mcInst.foobar = @"BAZ!")

is actually @"BAZ!", and not mcInst.foobar.

More detail is available below, but it might help to consider the following modification to your setFoobar method:

- (void) setFoobar:(NSString*)fbSet
{
    [_foobar release];
    _foobar = [[NSString stringWithFormat:@"HELLO_%@", fbSet] retain];
}

With this code in place, the value of the foobar property is modified while it is being set, but your line of code will still display the value 'BAZ!'.

Details:

As pointed out by newacct, your NSLog code works because you use the assignment operator (=), which has some very specific behaviour in the C language (which Objective-C is based upon)

In C, you can do the following:

x = y = z = 42;

and all of the variables, x, y and z will hold the value 42.

The compiler handles this behaviour by using a temporary variable(*). Essentially, what happens behind the scenes looks something like this:

tempVar = 42;
z = tempVar;
y = tempVar;
x = tempVar;

Along the same lines, you can do the following:

SomeFunction(x = 42);

this line of code will copy the value of 42 into x, and then call SomeFunction with an argument of 42. Behind the scenes, it looks like this:

tempVar = 42;
x = tempVar;
SomeFunction(tempVar);

Now, in Objective-C, your logging line is handled as follows:

tempVar = @"BAZ!";
[mcInst setFooBar:tempVar];
NSLog(@"I set 'foobar' to '%@'", tempVar);

(*) note that the usage of a "temporaray variable" I describe is meant to illustrate the concept, and may not actually reflect what any given compiler actually does under the hood. That sort of implementation detail is up to the programmers who write the compiler, and each one may do something different. The end result, however, is the same.

e.James
I understand the C behavior and how it is accomplished by the compiler. I suppose your point of the compiler using a temp variable makes sense, so thank you. However, the focus of my question was on the apparent contradiction between a method returning void (which is what the property syntax in Objective-C boils down to) and the assignment returning the assigned value.
rpj
Hmm. I think I may have buried the key concept underneath too much detail. If you take a look at the last code block in my answer, you can see why the use of a temporary variable makes the return value of the property accessor irrelevant. The property accessor is simply called as one of the steps in executing your `NSLog(...)` line, and it is the temporary variable which gets passed to `NSLog`.
e.James
I put the critical point at the top of my answer, and added another example which might help to illustrate. I hope it helps!
e.James
A: 

This is because of the way the C assignment operator works. As described in the ANSI C standard:

"An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment..."

Your assignment expression is mcInst.foobar = @"BAZ!". It seems to make sense to me that even though the assignment works by calling a method on mcInst the behaviour the same as with C. The value of the assignment expression is the left operand after the assignment (@"BAZ!") so this value is passed to the NSLog function.

This is the same behaviour that allows you to write an initialiser in the style of if (self = [super init]).

P.S. It is a fair question to ask why would the compiler call the setter on the property when assigning the value to it and not call the getter when using the value of mcInst.foobar afterwards. I'd say that it's simply assumed the getter will return the same value that was just assigned to the property and therefore the getter is not called.

szzsolt
+1  A: 

There's no need to call a getter — it has the value being assigned right there on the same line. You can think of it as expanding to [mcInst setFoobar:@"BAZ!"], @"BAZ!".

Chuck