Objective-C中的只读属性?


93

我在接口中声明了一个readonly属性,例如:

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

也许我误解了属性,但是我认为当您将其声明为时readonly,您可以在实现(.m)文件中使用生成的setter ,但是外部实体无法更改该值。这样的问题说那应该发生。这就是我的行为。但是,当尝试使用标准的setter或点语法eventDomain在我的init方法内部进行设置时,它给我一个unrecognized selector sent to instance.错误。我当然在@synthesize找房子。尝试像这样使用它:

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

那么我会误会readonly关于财产的声明吗?还是发生了其他事情?

Answers:


116

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

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
这不是类别。这是一个类扩展(如Eiko所说)。尽管它们用于类似目的,但它们明显不同。例如,您不能在真实的类别中执行上述操作。
bbum 2011年

2
我注意到,从具有类扩展属性的类继承的类将看不到它们。即继承只能看到外部接口。确认?
Yogev Shelly

5
这不是很公平的bbum。它可能有所不同,但是被称为“匿名类别”
MaxGabriel 2012年

3
这是op在公共接口中的初始声明之外的内容吗?含义是,此类扩展声明是否覆盖?中的初始声明.h?否则,我看不出这将如何暴露公众的二传手。感谢
Madbreaks'Dec 19'12

4
@Madbreaks这是附加功能,但在.m文件中。因此,编译器知道可以对该类进行读写,但是按预期,外部使用仍然限于只读。
Eiko 2012年

38

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 Compiler 4.0(Xcode 4.4中的新功能)开始,您无需弄混其他答案中讨论的琐事:

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

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

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

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

使用self->是访问对象/实例的成员变量的方法。您也许可以省略它,而只需使用var名称。但是我更喜欢使用self + arrow,因为它可以使我的代码自我记录。当您看到时,self->_foo您毫无疑问地知道_foo该实例上的成员变量。


顺便说一句,关于属性访问器与直接ivar访问的优缺点的讨论,正是您在Matt Neuberg博士的《Programming iOS》一书中所读到的。我发现阅读和重新阅读非常有帮助。


1
也许您应该补充一点:永远不要直接访问成员变量(从没有其类的情况下)!这样做违反了OOP(信息隐藏)。
2013年

2
@HAS正确,这里的场景是私下设置从此类之外看不到的成员变量。这就是原始发布者问题的全部要点:将属性声明为,readonly这样其他任何类都不能设置它。
罗勒·布尔克

我正在看这篇文章,了解如何创建只读属性。这对我不起作用。它的确允许我在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
您描述的第一种方式比接受的答案更容易处理。只是一个“ @synthesize foo1,foo2,foo3;”。在.m中,一切都很好。
sudo 2014年

20

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

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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.