19

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

基数.h:

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

基数:

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

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

子.h:

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

分米:

@implementation Sub
@end

和代码:

主.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?

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

分米:

@implementation Sub
@synthesize something = _something;
@end

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

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

有人可以告诉我财产覆盖发生了什么吗?

4

3 回答 3

31

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

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


如果你想从子类中对同一个实例变量进行写访问,你必须@protected在基类中声明它(以便它在子类中可以访问),在子类中重新声明属性为读写,并提供一个setter方法:

基数.h:

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

子.h:

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

分米:

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

您的解决方案

@synthesize something = _something;

在子类中生成getter和setter方法,在子类中使用单独的实例变量 _something_something与基类不同)。

这也有效,您只需要注意self.something在基类和子类中引用不同的实例变量。为了使这一点更明显,您可以在子类中使用不同的实例变量:

@synthesize something = _somethingElse;
于 2013-09-07T12:56:37.280 回答
5

给定的答案非常好。这是一个替代答案,显然 Apple更喜欢.

你可以为你的类定义一个私有扩展Base+Protected.h,一个文件,它需要包含在Base.m和中Sub.m

然后,在这个新文件中,将属性重新定义为readwrite.

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

这种替代方法允许您使用访问器self.something而不是 ivar _something

something注意:您仍然需要保持Base.h原样的定义。

于 2017-01-17T15:36:15.430 回答
-1

我猜想当属性没有在子类中合成时,支持变量是相同的。因此,在运行时,程序会尝试调用超类中的 setSomething。但是由于它不存在,因此会引发异常。

于 2013-09-07T12:55:59.053 回答