Objective-C:如何从子类访问父类属性?

48
如果我定义了这个类,如何在子类中访问someObject属性而不出现编译错误?
@interface MyBaseClass
  // someObject property not declared here because I want it to be scoped 
  // protected. Only this class instance and subclass instances should be
  // able to see the someObject property.
@end

// This is a private interface extension...properties declared here
// won't be visible to subclasses. However, I don't see any way to 
// declare protected properties...
@interface MyBaseClass (private)
   @property (nonatomic, readwrite, retain) NSObject *someObject;
@end

@interface MySubclass : MyBaseClass 
@end

@implementation MySubclass

- (id) init {
    // Try to do something with the super classes' someObject property. 
    // Always throws compile errors.

    // Semantic Issue: Property 'someObject' not found 
    // object of type 'MySubclass *'
    self.someObject = nil; 

}
@end



I'm obviously not understanding how inheritance works in objective-c. Could someone enlighten me?


1
@Elise - 我已经标记了你的评论,因为虽然它是正确的,但它忽略了关于属性继承如何工作的概念性问题。在忽略实际上OP正在询问的概念问题的情况下纠正代码错误是愚蠢、令人沮丧和毫无用处的。 - Chuck Wolber
5个回答

56
你需要的解决方案是在类扩展中声明 MyBaseClass 私有属性:
@interface MyBaseClass ()
@property (nonatomic, readwrite, retain) NSObject *someObject;
@end

你可以在 MyBaseClass 和 MySubclass 中都声明这个属性,这样 MySubclass 就能了解这些属性并且可以使用它们。

如果重复让你感到困扰,可以将类扩展放到一个独立的 .h 文件中,并将它导入到两个 .m 文件中。

我会给出我的代码示例,这是 MyDownloaderPrivateProperties.h 文件:

@interface MyDownloader ()
@property (nonatomic, strong, readwrite) NSURLConnection* connection;
@property (nonatomic, copy, readwrite) NSURLRequest* request;
@property (nonatomic, strong, readwrite) NSMutableData* mutableReceivedData;
@end

这个文件里面没有对应的.m文件,而且它本身就是一个纯声明的文件。现在这里是MyDownloader.m文件的开头:

#import "MyDownloader.h"
#import "MyDownloaderPrivateProperties.h"
@implementation MyDownloader
@synthesize connection=_connection;
@synthesize request=_request;
@synthesize mutableReceivedData=_mutableReceivedData;
// ...

这是其子类MyImageDownloader.m的开始:

#import "MyImageDownloader.h"
#import "MyDownloaderPrivateProperties.h"

问题已解决。隐私得到保护,因为这些是唯一导入MyDownloaderPrivateProperties.h的类,所以在编译器看来它们是唯一知道这些属性的类(这就是Objective-C中隐私的全部内容)。子类可以访问由超类合成的私有属性访问器。我相信这是您最初想要实现的。


这似乎是解决让子类访问超类的私有接口的一般问题的更直接的答案。但是,重复确实让我感到困扰。也许只需将属性公开... - CharlieMezak
对我来说,那似乎是最好的答案,而不是最初被接受的答案。 - Nir Golan

14

这就是如何访问它们。你遇到问题的地方在于如何声明它们:

@interface MyBaseClass : NSObject
@property (nonatomic, readwrite, retain) NSObject *someObject;
@end

这是声明新的Objective-C类的正常方法。

通过添加括号(而不是声明超类 - 在此示例中为NSObject),您已声明了一个类扩展,这可能对子类(通过包含)不可见。

您可能永远不需要在Objective-C中声明根类:

@interface MyBaseClass // << superclass omitted
@property (nonatomic, readwrite, retain) NSObject *someObject;
@end

除非您非常有经验并知道根类的用途,否则应将NSObject(或其子类,假设您的目标是苹果系统)作为基类。

类扩展通常用于“模拟”私有接口。 通过模拟,编译器不会像在其他语言中那样强制执行此操作。 例如,所有消息仍然是动态的,尽管子类可能(不知情地)覆盖您的扩展中使用相同选择器声明的方法。


2
遵循良好的封装设计,我将声明someObject属性为私有,因为它不应该从类层次结构外部访问。只有MyBaseClass和任何MyBaseClass的子类应该看到该属性。如果我在接口中声明该属性,那么我不是在将其变成公共属性吗? - memmons
1
这是正确的。该语言对方法的可见性没有控制,只有实例变量有。正如前面提到的,动态调度还允许子类重写任何方法,无论他们是否知道它。有时候这真的很痛苦,但这是语言的限制。你可以用几种方法来解决这个问题:1)使用一个C++的实现/实例变量,它可能提供你想要的所有可见性特性。2)在方法名称中注明该方法是受保护的。3)增加对公共使用的支持。4)将实现移到受保护的实例变量中。 - justin
2
Sherm - 我非常支持良好的设计,但如果语言不支持它,你还有什么选择?看起来我要么根本不使用属性,因为它们不支持任何类型的作用域,并使用 ivars,要么我可以对所有需要在子类中可见的内容使用公共属性,并记录它们应该如何使用。无论哪种情况,都会以某种方式破坏面向对象设计原则。 - memmons
1
@Sherm 我也会写一些 C++ 和 Objective-C 的代码。有时候我会不小心试图破坏它,但幸运的是编译器会检查并捕获这些错误。如果需要隐藏某些内容,强制实现更改也是很好的选择。在这种情况下,必须立即更新依赖项。程序由许多人维护多年,因此最好拥有工具和语言功能来验证设计的正确性随着程序的发展而演变,这是我的看法。 - justin
1
@Justin - 这是一个很好的观点,缺乏访问限制确实给程序员带来了更大的负担。伴随着强大的能力而来的是巨大的责任! :-) - Sherm Pendley
显示剩余5条评论

4
根据您的基类名称后面的括号,看起来您在类实现内声明了一个私有接口扩展,是这种情况吗?如果是,则该变量只能从该类实现中访问。
您的 MyBaseClass 直接继承自 NSObject 吗?
如果是这样,您需要在接口文件中声明 someObject 属性,如下所示:
@interface MyBaseClass : NSObject
{
}
@property (nonatomic, retain) NSObject *someObject;

然后像你已经做的那样综合它。

你和 Justin 提出了相似的观点 -- 我认为那就是答案。我只需要理解在封装考虑下,这个设计是如何起作用的。请看我的评论给 Justin 的答案。 - memmons
这个SO讨论中有一些相关的额外信息:https://dev59.com/7nM_5IYBdhLWcg3wiDqA - memmons
除了最初的操作(正如您现在所知道的那样,它有其局限性)之外,我不知道您是否有办法将@property声明为私有。在这种情况下,我可能会建议您在父类中生成自己的setter/getter方法。 - Rog
第二个链接是一个好主意 - 将其设置为只读公共并将其重新声明为私有的可读写。这是苹果对此的看法 - http://developer.apple.com/library/Mac/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW19 - Rog
1
@Harkonian 所有方法都可以通过运行时公开访问/调用 - 客户端只需要选择器的名称,有时还需要其签名。一些人通过在实现中声明方法(扩展是一个例子)来模拟访问限制。如果编译器无法找到具有匹配名称/签名的选择器,或者如果编译器可以推断出对象可能不会响应选择器,则编译器将发出警告。即使这些警告可能被绕过,客户端也可以调用该方法。 - justin
显示剩余3条评论

0

这是一个满足大部分目标的替代方案。

在您的头文件中定义接口。

@interface MyBaseClass : NSObject {
    NSObject *inheritableObject;
}
@property (readonly) NSObject *inheritableObject;

现在你可以在 MyBaseClass 中编辑 inheritableObject,以及任何继承自 MyBaseClass 的类中进行编辑。然而,在外部,它是只读的。不像 @interface MyBaseClass() 中的私有变量,而是受到不受控制的更改的保护。


-3

super.someObject = nil;。继承意味着MyBaseClass是你的super类。


尝试过了 - 相同的错误。无论如何,即使这样做可以工作,它也会很脆弱。如果你有一个深层次的层次结构怎么办?super.super.super.super.someObject? - memmons

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