@synthesize foo = _foo 是什么意思?

10
iOS 应用中为什么要使用下划线前缀?
4个回答

3
有时候了解幕后发生的事情可以帮助你理解。当你看到这个时,基本上是这样的:
@property (nonatomic, retain) Foo *foo;

在实施过程中

@synthesize foo=_foo;

这是一种语法糖,意味着编译器基本上会为您生成以下代码。
Foo *foo = nil;

-(Foo *) foo {
    return _foo;
}

-(void) setFoo:(Foo *)val {
    if( _foo != val ) {
        [_foo release];
        _foo = [val retain];
    }
}

这样,当您引用公共属性时,可以通过 self.foo 的生成访问器进行访问;如果要在类内部引用实例变量,则可以使用 _foo。在XCode 4之前,很容易混淆这两者。您可以执行以下操作:

self.foo = foo1;

foo = foo2;

这是完全合法的,但可能会在几个层面上引起问题。第二行没有使用访问器,因此foo2将不被保留,这可能导致它过早地被垃圾回收。更糟糕的是,第二行没有使用访问器,这将释放任何先前的值,这意味着它会导致内存泄漏。
因此,总之,新技术为您的属性创建了getter和setter,并允许您还指定用于实例变量的名称,用于封装。

好的解释。只是挑刺一下:if( _foo != nil ) [_foo release]; 编译器没有生成与 nil 的测试,这将是无用的,它更愿意测试新值是否与先前的值不同。请参见 https://dev59.com/5FHTa4cB1Zd3GeqPOxwY#3925204 了解示例。 - jv42
尝试使用 Foo *tmp = [Foo foo]; self.foo = tmp; self.foo = tmp; 运行您的代码,以查看其崩溃。 - jv42
我在iPad上匆忙地写下了那个答案,虽然我不是完全确定具体的实现方式,但我想真正强调它可以帮你处理内存管理并避免冲突。谢谢提醒! - jerrylroberts
jv42,在你的第二条评论中,Foo *tmp = [Foo foo]; 不应该编译通过,因为 -(Foo *)foo 在类级别上是不可访问的。如果方法签名被改为 +(Foo *)foo,那么 [Foo foo] 就是完全合法的,但情况并非如此。 - jerrylroberts
抱歉,我的意思是使用标准命名约定返回一个自动释放的 Foo 实例的方法。这里属性名称不符合这个约定。 - jv42

1
这是我有时这样做的原因:
如果我想在属性上进行懒加载,我通常会在我的实例变量前加下划线,以便从后端服务器/保存的文件/等等中加载所需数据的工作仅在第一次加载时完成。例如:
- (NSMutableArray *)recentHistory;
{
   if (_recentHistory == nil)
   {
       // Get it and set it, otherwise no work is needed... hooray!
   }

   return _recentHistory;
}

因此,对属性 [instanceOfClass recentHistory](或 instanceOfClass.recentHistory)的调用将检查 ivar 是否需要加载数据,或者只返回已经加载的数据。

这种方式声明所有属性可能有些过头了。

希望能有所帮助。


2
但是你真的必须使用下划线吗?还是只是作为一种约定?你可以只使用 @synthesize recentHistory; 并在 recentHistory 方法内部使用实例变量 recentHistoryself->recentHistory,只要确保不使用属性名称 self.recentHistory[self recentHistory] 以避免递归。 - Mattias Wadman
不,这是一种惯例。你也可以在苹果的代码中找到这种惯例,这可能就是人们使用它的原因。拥有一个iVar和一个属性的想法有时被过度使用,但在这种情况下,我认为它是有意义的。 - Ryan Crews

1

我只是想在所有上面的答案中加入我的想法。

在我的情况下,我主要使用它来保护我的类免受意外伤害。

在我的.h文件中,我仅声明属性而没有ivars。在.m文件中,我使用上述@synthesize模式将实际的ivar隐藏起来,包括我自己在内的用户,强制使用合成/动态访问器而不是直接使用ivar。您可以为ivar名称使用任何内容,而不仅仅是下划线。例如,您可以这样做:

@synthesize foo = _mySecretFoo;
@synthesize bar = oneUglyVarBecauseIHateMyBoss;

这样,你的老板只会看到bar,而你会发现使用bar访问器更容易、更安全,无论你使用点符号还是消息。

我更喜欢这种方法。

@property (getter=foo, setter=setFoo, ...) _mySecretFoo;
@property (getter=bar, setter=setBar, ...) oneUglyVarBecauseIHateMyBoss;

因为这不强制私有实现和封装,而且只是在Xcode可以为你完成相同的工作时多余的输入。要记住一件事,属性与实例变量(ivar)并不相同!你可以拥有比实例变量更多的属性,或者反过来。


如果我理解正确的话,您可以在@implementation {...}块中不显式声明ivars来完成这个操作。如果您永远不会引用oneUgly,那么使用@synthesize bar = oneUgly;有什么优势呢?为什么不只是使用@synthesize bar;?这是因为如果您写例如bar = 123.0,您的方法会出现错误吗? - Tim
你的意思是在 @interface {} 块中声明 ivars。如果使用 bar = 123,会出现错误,但这不是使用上述模式的原因。这种模式是一种方便的方式,可以公开接口,同时完全隐藏实现,就像苹果文档中的这个例子:@synthesize age = numberOfYears; 它还允许我这样做:SomeAccount *account = [SomeAccount defaultAccount]; self.account = account; 而不用担心错误,因为这是我的代码编写风格。这纯粹是方便和编码风格。 - Salam Horani

0

保持ivar(实例变量)安全是一种惯例,所以你只能通过getter和setter来访问它。


2
更好的规范是不使用点语法。 - jer
4
@synthesize(以及属性总体而言)与点语法完全没有关系。 - Catfish_Man

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接