在 Objective-C 中覆盖 @property 声明

Posted

技术标签:

【中文标题】在 Objective-C 中覆盖 @property 声明【英文标题】:Overriding @property declarations in Objective-C 【发布时间】:2011-08-16 22:36:24 【问题描述】:

我经常发现我知道基类的某个属性将始终是子类中的某个类型。例如,在下面的示例中,属性obj 在 Derived 中将始终是一个 NSString 对象。但是,我需要这个属性是 Base 类中更通用的 id 类型。

@interface Base
@property (strong, nonatomic) id obj;
@end

@implementation Base
//@synthesize obj = obj_;
@dynamic obj;
@end


@interface Derived : Base
@property (strong, nonatomic) NSString *obj;
@end

@implementation Derived
@synthesize obj = obj_;
@end

这段代码正确吗?我担心@synthesize 出现两次。这是创建两个属性,还是 Derived 中的 @synthesize 声明覆盖 Base 中的那个?

编辑:在 Base 中将 @synthesize 更改为 @dynamic 更有意义。

编辑:这需要 ios SDK 5。

【问题讨论】:

出于好奇,如果您获取 Derived 的实例,将其强制转换回 Base,然后将非字符串对象分配给该属性,您期望会发生什么?换句话说,类似于((Base *)myDerived).obj = [NSNumber numberWithInt:3] Re 上面的“出于好奇”,这相当于说,“如果您将 Derived 的实例转换为 (UICollectionViewController *)myDerived 然后......键入它不是,不要期望好的结果。这包括不能将子类视为其超类。 @KevinBallard 感谢您的评论。简明扼要。 【参考方案1】:

子类可以改变与方法相关的类型。通常,子类可以专门化返回类型,并且可以使参数类型更通用。这个其实有一个名字,但我不记得它是什么了。无论如何,这是理性的:

返回类型

如果我有课

@interface A
- (id)foo;
@end

和另一个班级

@interface B : A
- (NSString *)foo;
@end

我有一个实例B* b,我可以将它转换为A*,并且仍然符合-[A foo]方法的类型签名,因为任何NSString*也是一个id

但是,我不能更概括地说明这一点。如果相反,我有

@interface A
- (NSString *)foo;
@end

@interface B : A
- (id)foo;
@end

我有一个实例B* b,我将其转换为A*,然后[(A*)b foo] 的类型是NSString *,但实际值可能是任何id,因为这是我声明的类型-[B foo] 是。这违反了类型系统。

参数

如果我有课

@interface A
- (void)foo:(NSString *)obj;
@end

和另一个班级

@interface B : A
- (void)foo:(id)obj;
@end

我有一个实例B* b,我将其转换为A*,然后[(A*)b foo:obj] 的任何有效参数也符合-[B foo:] 的类型,因为任何NSString * 也是id .

但是,如果我有以下情况

@interface A
- (void)foo:(id)obj;
@end

@interface B : A
- (void)foo:(NSString *)obj;
@end

我有一个实例B* b 并将其转换为A*,然后我可以将任何id 传递给[(A*)b foo:obj],但底层类B 只需要NSString*s。因此我违反了类型系统。

属性

这里是关键点。当你声明一个属性的类型时,你同时声明了getter的返回类型setter的参数类型。根据上述规则,这意味着您不能更改属性的类型,因为在这两种情况之一中,您将违反类型系统。


以上是理论。在实践中,我不知道 GCC 或 Clang 是否强制执行这些约束。他们可能认为程序员最了解,并且不正确地概括或专门化一个类型会默默地破坏你背后的类型系统。你必须进行实验。但是,如果编译器确实正确,那么它将不允许泛化返回类型和专门化参数。这意味着它将不允许更改属性的类型。

即使编译器允许,您也可能不应该这样做。悄悄地破坏类型系统是引入错误的好方法,也是糟糕架构的一个指标。

【讨论】:

很好的答案,但不幸的是,Clang 没有执行那里的规则。 “实际上有一个名称,但我不记得它是什么” -> 逆变方法参数类型? en.wikipedia.org/wiki/… @DiegoFreniche 是的,方法参数是逆变的,方法返回类型是协变的。 函数返回/参数类型的协变/逆变是function subtyping 的规则,也是Liskov Substitution Principle 的结果,如果其中任何一个是这样的话。【参考方案2】:

你实际上不能这样做。对我来说,我收到一个错误

property 'obj' attempting to use ivar 'obj_' declared in super class of 'Derived'

所以我认为这很明显。而且即使你不使用@synthesize 并自己定义函数,也只会调用派生版本(ObjC 中没有函数重载或虚函数之类的东西)。

你必须检查你的班级设计。

【讨论】:

您需要使用 iOS SDK 5。 好的,但这仍然不是最好的方法。 这个特殊的错误仅仅是因为您在Derived 中有一个@synthesize 引用了在超类中声明的ivar。如果你摆脱第二个@synthesize 会发生什么? 哪个编译器? GCC、LLVM-GCC 还是 Clang?听起来编译器没有像它应该的那样强制执行类型约束(有关详细信息,请参阅我的答案)。 回答你的问题:LLVM/Clang

以上是关于在 Objective-C 中覆盖 @property 声明的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 中覆盖 Objective-c 类函数?

在 Objective-C 中覆盖 @property 声明

在objective-c或swift中覆盖pod文件方法的最佳实践是啥?

Programming with Objective-C

使用可覆盖属性时如何按命令行卸载 MSI 包?

数据隐藏和 Objective-C 合成器 [重复]