Objective-C property assignment returns the assigned value?
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.
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.
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!"
.
in C, assignment is an expression, which evaluates to the assigned value
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.
精彩评论