【问题标题】:Objective-C property assignment returns the assigned value?Objective-C 属性赋值返回赋值?
【发布时间】:2010-01-13 21:02:20
【问题描述】:

假设我有以下内容:

@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

然后:

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

查看-[MyClass setFoobar:] 的返回值,可能会假设此行将打印I set 'foobar' to '',因为赋值似乎没有返回任何内容。

但是 - 谢天谢地 - 此分配按预期运行,代码打印为 I set 'foobar' to 'BAZ!'。不幸的是,这感觉像是一个矛盾,因为调用的 setter 的返回值掩盖了赋值返回分配给它的值的事实。起初我认为mcInst.foobar = @"BAZ!"; 是在进行两次调用而不是一个块:首先是 setter,然后是 getter 以收集返回值。但是,使用 NSLog 调用检测 setter 和 getter 方法证明情况并非如此。

【问题讨论】:

  • 我认为不是“为什么 NSLog 工作?”,一个更好的问题(我认为您要问的问题)是“为什么 setter 返回 void,而不是(在这种情况下) NSString* ? 我还假设 mcInst.foobar = 只是 [mcInst setFoobar: ...] 的语法糖,我猜不是...
  • 是的,问题是为什么编译器在你使用NSLog(@"I set 'foobar' to '%@'", [mcInst setFoobar: @"BAZ!"]); 时会抛出错误,承认该方法不返回任何内容,但允许你以其他方式执行。
  • Brian:是的,这实际上是我想问的问题,虽然我的措辞可能隐藏了那个 =)
  • Dimitris:你的例子完美地说明了我的问题。如果属性语法确实只是简单地编译为消息发送,并且该消息返回 void,那么它为什么会起作用?!
  • 我从这次对话中了解到,属性语法确实编译为[mcInst setFoobar: @"BAZ!"]。而且我相信 e.James 的回答准确地显示了编译器的作用。感谢您提供有用的帖子。

标签: objective-c gcc compiler-construction objective-c-runtime


【解决方案1】:

快速总结:

这里的快速回答是没有矛盾,因为表达式的结果:

(mcInst.foobar = @"BAZ!")

实际上是@"BAZ!"不是mcInst.foobar

下面提供了更多详细信息,但考虑对您的 setFoobar 方法进行以下修改可能会有所帮助:

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

使用此代码,foobar 属性的值在设置时被修改,但您的代码行仍将显示值 'BAZ!'

详情:

正如newacct 所指出的,您的 NSLog 代码之所以有效,是因为您使用了赋值运算符 (=),它在 C 语言(Objective-C 所基于)中具有一些非常具体的行为

在 C 中,您可以执行以下操作:

x = y = z = 42;

所有变量,xyz 将保持值 42。

编译器通过使用临时变量 (*) 来处理此行为。本质上,幕后发生的事情是这样的:

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

同样,您可以执行以下操作:

SomeFunction(x = 42);

这行代码会将 42 的值复制到 x 中,然后调用 SomeFunction,参数为 42。在幕后,它看起来像这样:

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

现在,在 Objective-C 中,您的日志记录行如下处理:

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

(*) 请注意,我描述的“临时变量”的使用是为了说明这个概念,并且可能实际上并不能真正反映任何给定编译器实际上在幕后所做的事情。这种实现细节取决于编写编译器的程序员,每个人都可能做不同的事情。然而,最终的结果是一样的。

【讨论】:

  • 我了解 C 行为以及编译器如何完成它。我想您对使用临时变量的编译器的观点是有道理的,所以谢谢。但是,我的问题的重点是返回 void 的方法(Objective-C 中的属性语法归结为)和返回分配值的赋值之间的明显矛盾。
  • 嗯。我想我可能将关键概念隐藏在太多细节之下。如果您看一下我回答中的最后一个代码块,您就会明白为什么使用临时变量会使属性访问器的返回值变得无关紧要。属性访问器被简单地称为执行NSLog(...) 行的步骤之一,它是传递给NSLog 的临时变量。
  • 我把关键点放在答案的顶部,并添加了另一个可能有助于说明的例子。希望对您有所帮助!
【解决方案2】:

没有必要调用getter——它的值就在同一行被赋值。您可以将其视为扩展为[mcInst setFoobar:@"BAZ!"], @"BAZ!"

【讨论】:

    【解决方案3】:

    在 C 中,赋值是一个表达式,计算结果为赋值

    【讨论】:

    • 我理解,甚至指出了这一点。我正在寻找Objective-C的实现细节。
    【解决方案4】:

    这是因为 C 赋值运算符的工作方式。如 ANSI C 标准中所述:

    "赋值运算符将值存储在由指定的对象中 左操作数。赋值表达式具有左边的值 赋值后的操作数..."

    你的赋值表达式是mcInst.foobar = @"BAZ!"。对我来说似乎很有意义,即使通过调用 mcInst 上的方法进行赋值,其行为与 C 相同。赋值表达式的值是赋值后的左操作数 (@"BAZ!"),因此传递了此值到 NSLog 函数。

    这与允许您以if (self = [super init]) 的样式编写初始化程序的行为相同。

    附:一个公平的问题是,为什么编译器在为其赋值时调用属性的 setter,而在之后使用 mcInst.foobar 的值时不调用 getter。我想说的是,它只是假设 getter 将返回刚刚分配给属性的相同值,因此不会调用 getter。

    【讨论】:

      猜你喜欢
      • 2014-03-24
      • 1970-01-01
      • 2012-06-16
      • 2013-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多