Objective-C中的只读属性?

99

我在接口中声明了一个只读属性,如下所示:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

也许我对属性的理解有误,但我认为当你将其声明为readonly时,你可以在实现(.m)文件内使用生成的setter,但外部实体无法更改该值。这个SO问题说应该是这样的行为。这就是我想要的行为。然而,在尝试在我的init方法内使用标准的setter或点语法设置eventDomain时,它会给出一个unrecognized selector sent to instance.错误。当然,我已经用@synthesize合成了属性。我尝试像这样使用它:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

那么,我是否误解了属性上的readonly声明?还是有其他事情发生了?

7个回答

122
你需要告诉编译器你也想要一个setter方法。一种常见的方法是将其放在.m文件中的类扩展中:

放在类扩展中即可。

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

23
这不是一个范畴,而是一个类扩展(就像Eiko所说的)。虽然用于类似的目的,但它们是明显不同的。例如,在真正的范畴中无法执行上述操作。 - bbum
2
我注意到继承自具有类扩展属性的类的类将无法看到它们。也就是说,继承只能看到外部接口。确认吗? - Yogev Shelly
5
不太公平,bbum。这可能有所不同,但它被称为“匿名类别”。 - MaxGabriel
3
这是在公共接口中的初始声明之外吗?也就是说,这个类扩展声明是否覆盖了 .h 中的初始声明?否则,我不知道如何公开一个 setter。谢谢。 - Madbreaks
4
额外的内容在.m文件中,编译器会将其读写属性设置为该类,但是对于外部使用,仍然只能按照预期设置为只读。 - Eiko
显示剩余2条评论

40

Eiko和其他人给出了正确的答案。

这里有一个更简单的方法:直接访问私有成员变量。

示例

在头文件.h中:

@property (strong, nonatomic, readonly) NSString* foo;

在实现文件.m中:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

就这些,你所需要的全部。不必烦恼,不必费心。

细节

从Xcode 4.4和LLVM编译器4.0(Xcode 4.4中的新功能)开始,你就不需要再处理其他答案中讨论的琐事:

  • synthesize关键字
  • 声明变量
  • 在实现.m文件中重新声明属性。

在声明属性foo之后,你可以假设Xcode已经添加了一个以下划线为前缀命名的私有成员变量:_foo

如果该属性被声明为readwrite,则Xcode生成一个名为foo的getter方法和一个名为setFoo的setter方法。当使用点符号表示法(my Object.myMethod)时,这些方法会被隐式调用。如果该属性被声明为readonly,则不会生成setter。这意味着以下划线命名的后备变量本身并不是只读的。 readonly仅意味着没有合成setter方法,因此尝试使用点符号表示法设置值将导致编译器错误。点表示法失败是因为编译器阻止你调用一个不存在的方法(setter)。

最简单的解决方法是直接访问以下划线命名的成员变量。即使没有声明那个下划线命名的变量,你也可以这样做!Xcode会在构建/编译过程中插入该声明,因此您的编译代码确实具有变量声明。但是,您永远不会在原始源代码文件中看到该声明。这不是魔术,而是语法糖

使用self->是一种访问对象/实例的成员变量的方法。你可能可以省略它,并直接使用变量名。但我更喜欢使用self+箭头,因为它使我的代码自我记录。当你看到self->_foo时,你就知道_foo是这个实例上的成员变量,而没有歧义。


顺便说一句,在属性访问器和直接ivar访问之间的优缺点讨论正是你将在Dr. Matt NeubergProgramming iOS书中阅读到的深思熟虑的处理方式。我发现这非常有帮助,值得一读。


1
也许你应该补充说明,永远不要直接访问一个成员变量(从其类之外)!这样做是面向对象编程的违规行为(信息隐藏)。 - HAS
2
@HAS 正确,这里的情况是在类外部看不到的情况下私有地设置成员变量。这就是原帖提问的全部意义:将属性声明为“只读”,以便没有其他类可以设置它。 - Basil Bourque
我在查看如何创建只读属性的帖子。但是这对我不起作用。它确实允许我在init中分配值,但我也可以通过赋值稍后更改_variable。它不会引发编译器错误或警告。 - netskink
@BasilBourque 非常感谢您对“持久性”问题的有用编辑! - GhostCat

36

我发现另一种处理只读属性的方法是使用 @synthesize 指定支持存储。例如:

@interface MyClass

@property (readonly) int whatever;

@end

然后在实现中

@implementation MyClass

@synthesize whatever = m_whatever;

@end

由于m_whatever是成员变量,因此您的方法可以设置它。


我在过去几天中意识到的另一件有趣的事情是,可以创建只读属性,并使子类能够写入,如下所示:

(在头文件中)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

然后,在实现中

@synthesize myProperty = m_propertyBackingStore;

它将使用标头文件中的声明,因此子类可以更新属性的值,同时保留其只读性。

然而,这在数据隐藏和封装方面略有遗憾。


1
你描述的第一种方式比被接受的答案更容易处理。只需要在 .m 文件中加上 "@synthesize foo1, foo2, foo3;",一切都很好。 - sudo

20

请参阅iOS文档中的自定义现有类

readonly 表示该属性只能读取。 如果您指定为readonly,那么在@ implementation 中只需要getter方法。如果在实现块中使用 @synthesize,则只会合成getter方法。此外,如果您尝试使用点语法分配值,则会收到编译器错误。

只读属性只有一个getter方法。 您仍可以在属性的类内部直接设置支持ivar或使用键值编码。


9
你误解了另一个问题。在那个问题中,有一个类扩展声明如下:(链接)
@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

这就是使setter只在类的实现中可见的原因。所以,正如Eiko所说,您需要声明一个类扩展并覆盖属性声明,告诉编译器只在类内生成setter。


5
最简单的解决方案是:
MyClass.h
@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2
如果一个属性被定义为只读,那么这意味着在类内部或其他类外部都不能使用setter。(也就是说:如果有意义的话,你只能使用“getter”操作。)
从听起来的意思来看,你想要一个普通的读/写属性,它标记为私有,你可以通过在接口文件中将类变量设置为私有来实现:
@private
    NSString* eventDomain;
}

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