覆盖子类中的readonly属性

时间:2013-09-07 12:25:14

标签: objective-c properties

有一个类看起来像这样(我为了简洁省略了导入):

Base.h:

@interface Base : NSObject
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;
@end

Base.m:

@implementation Base
- (id)initWithSomething:(NSString *)something {
    self = [super init];
    if (self) _something = something;
    return self;
}
@end

如您所见,'某事'属性是只读的。现在我想创建一个子类来覆盖该属性也是可写的:

Sub.h:

@interface Sub : Base
@property (strong) NSString *something;
@end

Sub.m:

@implementation Sub
@end

代码:

main.c中:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Sub *o = [Sub new];
        o.something = @"foo";
        NSLog(@"%@", o.something);
    }
    return 0;
}

此代码导致:

    2013-09-07 13:58:36.970 ClilTest[3094:303] *** Terminating app due to uncaught
    exception 'NSInvalidArgumentException', reason: '-[Sub setSomething:]: unrecognized
    selector sent to instance 0x100109ff0'

为什么?为什么找不到setSelector?

当我在子类中执行此操作时:

Sub.m:

@implementation Sub
@synthesize something = _something;
@end

一切正常。这是否意味着即使在@property中定义为@interface,子类'属性也不会默认合成?编译是否以某种方式'看到'从Base自动生成的getter并且不生成setter?为什么,我认为应该生成setter,因为它还不存在。我正在使用Xcode 4.6.2,该项目是一个Cli工具(类型基础),但在我的实际项目中也是如此,这是一个iPhone应用程序。

背景:我有一个重型对象(Base的实例)需要蓝牙连接到某些设备,我应该为某些功能创建一个视图控制器。为了便于测试,我不想连接到BT(实际上,我需要一个物理设备并在其上测试代码),我希望能够在模拟器中测试它。 我想到的是我只是创建了一个子类(Sub)来存根一些方法/属性并使用它代替,当代码准备就绪时我只需删除子类的代码,用正确的代码替换它的实例,用设备测试,提交和推送。它实际上工作正常,除了上面@property的奇怪之处。

有人可以告诉我房产重叠是怎么回事吗?

3 个答案:

答案 0 :(得分:28)

对于readonly属性,只合成一个getter方法,但没有setter方法。

编译子类时,编译器不知道 属性是如何实现的 在基类中(它可以是自定义getter而不是后备实例变量)。 所以它不能只在子类中创建一个setter方法。


如果您希望对子类中的相同实例变量具有写入权限, 你必须在基类中将它声明为@protected (以便可以在子类中访问),重新声明属性  作为子类中的读写,并提供一个setter方法:

Base.h:

@interface Base : NSObject {
@protected
    NSString *_something;
}
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;
@end

Sub.h:

@interface Sub : Base
@property (strong, readwrite) NSString *something;
@end

Sub.m:

@implementation Sub
-(void)setSomething:(NSString *)something
{
    _something = something;
}
@end

您的解决方案

@synthesize something = _something;

使用单独的实例在子类中生成getter和setter方法 子类中的变量 _something(不同 来自基类中的_something

这也有效,你应该知道self.something指的是 基类和子类中的不同实例变量。为此 更明显的是,你可以在子类中使用不同的实例变量:

@synthesize something = _somethingElse;

答案 1 :(得分:4)

给出的答案完全正常。这是一个替代答案,显然是Apple likes a bit more

您可以定义一个private extension的班级Base+Protected.h文件,该文件需要包含在Base.mSub.m中。

然后,在此新文件中,您将该属性重新定义为readwrite

@interface Base ()
@property (strong, readwrite) NSString *something;
@end

此备选方案允许您使用访问者self.something而不是ivar _something

注意:您仍然需要保留somethingBase.h的定义。

答案 2 :(得分:-1)

我猜想,当子类中没有合成属性时,后备变量是相同的。所以在运行时,程序试图调用超类中的setSomething。但由于它不存在,因此会抛出异常。