“property”、“_property”、“self.property”和“self._property”在读写属性和只读属性中有什么区别?

3

我希望能够很好地理解它,但迄今为止我没有找到一个集中考虑所有可能性的复杂答案。我知道在现代的 Objective-C 中,我们不会在 @implementation Album{ } 之间创建 ivars。

只是为了测试,我创建了 Album.h

@interface Album : NSObject

@property (nonatomic, copy, readonly) NSString *title, *artist;
@property (nonatomic, copy) NSString *title2, *artist2;

- (id)initWithTitle:(NSString*)title;

@end

Album.m 中:

@implementation Album

- (id)initWithTitle:(NSString*)title {
    self = [super init];
    if (self) {

        //READ-ONLY
        title = title; //1, still nil after compile
        _title = title; //2,
        self.title = title; //3, "assignment to readonly property"
        self._title = title; //4, "property '_title' not found"

        artist = @"Shakira"; //5, "use of undeclared identifier 'artist'"
        _artist = @"Shakira"; //6
        self.artist = @"Shakira"; //7, "assignment to readonly property"
        self._artist = @"Shakira"; //8, "property '_artist' not found"

        //READ-WRITE
        title2 = title; //9, "use of undeclared identifier 'title2'"
        _title2 = title; //10
        self.title2 = title; //11
        self._title2 = title; //12, "property '_title2' not found"

        artist2 = @"Shakira"; //13, "use of undeclared identifier 'artist2'"
        _artist2 = @"Shakira"; //14
        self.artist2 = @"Shakira"; //15
        self._artist2 = @"Shakira"; //16, "property '_artist2' not found"

    }
    return self;
}

@end

现在我正在使用它:

Album *album = [[Album alloc] initWithTitle:@"Live from Paris"];

问题如下:
  1. 为什么找不到4、8、12、16的属性,而2、6、10、14存在?
  2. 10和11或14和15有什么区别?
  3. 我在1中实际上做了什么?
  4. 我在哪里声明了2、6、10、14这些属性?
  5. 为什么我可以在2、6中分配只读属性,但不能在3、7中分配?
2个回答

4
当你输入时,

self.title = @"foo";

IT是真正的速记,代表着信息技术。

[self setTitle:@"foo" ];

你不需要实现
-(void)setTitle:(NSString *)title 

编译器已经为您合成了它。如果您能看到它放置的内容,它可能会像这样。
-(void)setTitle:(NSString *)title{
_title = [title copy];
}

所以您可以看到,_title是一个实例变量。而title不是,它是一个返回NSString的方法。它已经为您合成了,但它看起来像这样:

-(NSString *)title{
return _title;
}

它们是根本不同的东西。如果标题是只读的,那么setTitle:方法就永远不会被合成,尽管_title iVar仍然存在。因此,当您键入self.title = @“bar”时,编译器会查找-(void)setTitle..但它不存在。->不满意的编译器
最好的做法是避免直接使用下划线变量(称为backing iVars)。始终使用self.title = @“someTitle”而不是直接访问iVar。如果您想要使用键值观察,这是Cocoa最好的功能之一,那么您将会感到高兴。

所以,结论是:对于可读写的属性,始终使用:self.title = @"value",但是只读属性呢?当我输入self.title = @"value"时,会出现错误。 - Bartłomiej Semańczyk
对于只读属性,如果您愿意,确实可以直接设置_title iVar。在实践中,只读属性甚至可能没有后备iVar,它们可能在getter中计算。例如,矩形可能具有宽度和高度的读写属性,但面积是只读属性。我们不保留变量,因为每次setWidth或setHeight都需要更新它,这将非常麻烦。相反,我们自己实现面积 -(CGFloat)area {return self.width * self.height;} - Jef
无论如何,对于只读属性来说,KVO 不太可能成为一个问题,为什么要关注你不允许任何人更改的东西呢 :) - Jef
-(CGFloat )area{ return self.width*self.height; } - 这是只读属性的示例,仅具有getter,其中“get {}”是可选的。 我是对的吗? 在这种情况下,我们不必为readonly属性分配初始值,因为该属性是计算得出的。 - Bartłomiej Semańczyk
没错,所以不需要任何内存来存储任何东西。这只是一个我们都能理解的真正快速的例子,这可能是一个更复杂的计算,只有我们的类知道如何根据其其他数据成员进行计算。 - Jef

4

一些开头的解释

A. @property 声明 了名称为-property的方法,如果它不是只读的,则还声明了一个-setProperty:的方法。其他任何事情都不是由@property本身完成的!

B. @property 不会合成一个实例变量。什么?再说一遍!@property 不会合成一个实例变量。(我可以无限重复这句话。)

当您使用明确的@synthesize或自动综合时,将合成实例变量。但仍然存在(隐式,未显示的)合成!

要有隐式合成,必须满足以下条件:

  • 您需要一个@property。
  • 您没有手动实现访问器。

由于您可以在源代码中看到第一个条件,而无法看到第二个条件,因此很容易认为@property合成了实例变量。但这是不正确的。我们来试试:

@interface Foo : NSObject
@property NSString *foo;
@end

@implementation Foo
// Both accessors are implemented -> no auto synthesize -> no ivar
- (NSString*)foo
{
  return _foo; // Error: No ivar _foo
}

- (NSString*)setFoo:(NSString*)foo
{
  _foo=foo; // Error: No ivar _foo
}
@end

原因是:如果 @property 本身将合成 ivar,那么就不可能有一个声明的属性没有与 ivar 相对应。
(此外,如果您已经有一个符合要求的 ivar,则不会合成任何 ivar。但我们在这里不需要它。)
C. 合成的 ivar 具有标识符 _property,而不是 property。 (在某些情况下,没有下划线的现有 ivar 将被视为已存在的 ivar,但在这里我们不需要。)
D. 点符号表示法不访问 ivar。 它发送一条消息。 如果语句是左值,则使用 setter,如果是右值,则使用 getter。 您可以简单地将其翻译为:
self.property = …; // [self setProperty:…];
… = self.property; // … = [self property];

为什么找不到4、8、12、16属性,而存在2、6、10、14?
在4处,您使用点表示法,使用选择器set_Property发送消息。由于没有这样的方法,因为合成的方法是-setProperty::Error。
在2处,您直接访问ivar。它的标识符是_property。一切正常。
10和11或14和15之间有什么区别?
在11和15处,您使用setter设置属性的值。在10和14处,您直接设置ivar。 setter的代码不会执行。
让我们举个例子:
@interface Foo : NSObject
@property NSString *foo;
@end

@implementation Foo
// ivar _foo is (auto) synthsized, because there is no getter.
- (void)setFoo:(NSString*)foo
{
  NSLog( @"Setter executed");
  _foo = foo;
}
@end

在第10行和14行,你将看不到日志,因为setter没有执行。在第11行和15行,你将看到一个日志。
如果你在setter(或getter)中有其他事情要做,除了只设置ivar之外,结果会有所不同。
“我在1中实际上做了什么?”title不是一个ivar(ivar的标识符是_title),而是一个参数变量。(看一下方法定义的头部。)你将参数变量的值分配给它本身,这是一种毫无意义的操作。
你没有给ivar分配任何值,因为你没有使用ivar。
“我在哪里声明了属性2、6、10、14?”请参见A和B上面的内容。
“为什么我可以在2、6中分配只读属性,但不能在3、7中?”ivar从来不是只读的。readonly和readwrite选项用于方法声明。(参见A-C上面的内容。)
在2、6中,你直接设置ivar。它不能是只读的,所以一切都很好。
在3、7中,你向一个不存在的方法发送消息,因为该属性是只读的。

所以无法直接访问属性 title2 = @"value",只能通过访问器或它的 iVar 访问? - Bartłomiej Semańczyk
那么我没有设置iVars,但如果@property不创建它们,它们会在我的代码的哪个时刻被创建? - Bartłomiej Semańczyk
@BartłomiejSemańczyk,我为那个问题添加了一个示例。 - Amin Negm-Awad
我的意思是,你说“在11点15分直接设置了ivar”。你确定吗? - Bartłomiej Semańczyk
啊,正好相反。我改一下。 - Amin Negm-Awad
显示剩余2条评论

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