我已经看到了一些在 Objective-C 中声明半私有方法的策略,但似乎没有办法制作真正的私有方法。我接受。但是,为什么会这样呢?我基本上都说过的每一个解释,“你不能这样做,但这里是近似的。”
有许多关键字适用于控制其范围的ivars
(成员),例如@private
,@public
,@protected
。为什么不能这样做呢?它似乎是运行时应该能够支持的东西。我缺少一种潜在的哲学吗?这是故意的吗?
答案 0 :(得分:102)
答案是......嗯......简单。事实上,简单性和一致性。
Objective-C在方法调度时是纯动态的。特别是,每个方法调度都经历与每个其他方法调度完全相同的动态方法解析点。在运行时,每个方法实现都具有完全相同的暴露,并且Objective-C运行时提供的所有API与方法和选择器一起使用在所有方法中的工作方式相同。
许多人已经回答(在这里和其他问题),支持编译时私有方法;如果一个类没有在其公共可用接口中声明一个方法,那么就您的代码而言,该方法可能也不存在。换句话说,通过适当地组织项目,您可以在编译时实现所需的所有可见性组合。
将相同的功能复制到运行时几乎没有什么好处。这会增加大量的复杂性和开销。即使具有所有这些复杂性,它仍然不会阻止除了最随意的开发人员以外的所有人执行你所谓的“私人”方法。
但是,这样做会破坏语言的纯粹动态性质。每个方法调度都不再需要通过相同的调度机制。相反,你会处于这样一种情况,即大多数方法只有一种方式,少数方法只是不同。编辑:我所做的一个假设 注意到私人消息会 必须经历运行时 导致潜在的大 高架。这绝对是真的吗?
是的,确实如此。没有理由认为类的实现者不希望在实现中使用所有Objective-C功能集,这意味着必须发生动态分派。 然而,没有特别的理由说明为什么私有方法不能被
objc_msgSend()
的特殊变体调度,因为编译器会知道它们是私有的;也就是说,这可以通过向Class
结构添加一个私有方法表来实现。私人没有办法 这种检查或短路的方法 跳过运行时?
它无法跳过运行时,但运行时不会必须对私有方法进行任何检查。
那就是说,没有理由第三方不能故意在一个对象上调用
objc_msgSendPrivate()
,在该对象的实现之外,有些事情(例如KVO)必须这样做。实际上,它只是一个约定,在实践中比在私有方法的选择器前面添加或在接口头中不提及它们更好。
这延伸到运行时之外,因为Cocoa中有许多机制建立在Objective-C的一致动态之上。例如,密钥值编码和密钥值观察都必须经过大量修改才能支持私有方法 - 最有可能是通过创建可利用的漏洞 - 或私有方法不兼容。
答案 1 :(得分:18)
运行时可以支持它,但成本会很高。发送的每个选择器都需要检查该类是私有的还是公共的,或者每个类需要管理两个单独的分派表。这与实例变量不同,因为这种保护级别是在编译时完成的。
此外,运行时需要验证私人消息的发送者是否与接收者属于同一类。你也可以绕过私人方法;如果类使用instanceMethodForSelector:
,它可以将返回的IMP
提供给任何其他类,以便它们直接调用私有方法。
私有方法无法绕过邮件调度。请考虑以下情形:
班级AllPublic
有一个公开实例方法doSomething
另一个类HasPrivate
有一个名为doSomething
的私有实例方法
您创建的数组包含AllPublic
和HasPrivate
您有以下循环:
for (id anObject in myArray)
[anObject doSomething];
如果从AllPublic
内运行该循环,则运行时必须停止在doSomething
实例上发送HasPrivate
,但是如果它在HasPrivate
内,则该循环可用。 {1}}课程。
答案 2 :(得分:13)
到目前为止发布的答案很好地从哲学角度回答了这个问题,所以我将提出一个更实用的理由:通过改变语言的语义会得到什么?它足够简单,可以有效地“隐藏”私有方法。举例来说,假设您在头文件中声明了一个类,如下所示:
@interface MyObject : NSObject {}
- (void) doSomething;
@end
如果您需要“私有”方法,您也可以将其放在实现文件中:
@interface MyObject (Private)
- (void) doSomeHelperThing;
@end
@implementation MyObject
- (void) doSomething
{
// Do some stuff
[self doSomeHelperThing];
// Do some other stuff;
}
- (void) doSomeHelperThing
{
// Do some helper stuff
}
@end
当然,它与完全与C ++ / Java私有方法相同,但它实际上足够接近,所以为什么要改变语言的语义,以及编译器,运行时等等,添加已经以可接受的方式模拟的功能?正如其他答案所述,消息传递语义 - 以及它们对运行时反射的依赖 - 会使处理“私有”消息变得非常重要。
答案 3 :(得分:7)
最简单的解决方案是在Objective-C类中声明一些静态C函数。这些只有静态关键字的C规则的文件范围,因此它们只能由该类中的方法使用。
根本没有大惊小怪。
答案 4 :(得分:6)
是的,可以通过利用编译器已经采用的技术处理C ++而不影响运行时来完成它:名称修改。
尚未完成,因为尚未确定它将解决编码问题空间中的一些相当大的困难,其他技术(例如,前缀或下划线)能够充分规避。 IOW,你需要更多的痛苦来克服根深蒂固的习惯。
您可以为clang或gcc提供补丁,为语法添加私有方法,并生成在编译期间单独识别的错误名称(并立即忘记)。然后,Objective-C社区中的其他人将能够确定它是否真的值得。它可能比试图说服开发者更快。
答案 5 :(得分:4)
本质上,它与Objective-C的消息传递形式的方法调用有关。任何消息都可以发送到任何对象,并且对象选择如何响应消息。通常它会通过执行以消息命名的方法来响应,但它也可以通过许多其他方式进行响应。这并没有使私有方法完全不可能 - Ruby使用类似的消息传递系统来做 - 但它确实使它们有些尴尬。
即使是Ruby的私有方法实现也会让人感到困惑,因为它很奇怪(你可以发送任何你喜欢的消息,除了这个列表上的消息!)。从本质上讲,Ruby通过禁止使用显式接收器调用私有方法来使其工作。在Objective-C中,由于Objective-C没有这个选项,因此需要更多的工作。
答案 6 :(得分:1)
Objective-C的运行时环境存在问题。 While C/C++会编译为无法读取的机器代码Objective-C still maintains some human-readable attributes like method names as strings。这使Objective-C能够执行reflective功能。
编辑:作为一种没有严格私有方法的反思语言,Objective-C更加“pythonic”,因为您信任使用您的代码的其他人,而不是限制他们可以调用的方法。使用双下划线等命名约定意味着将代码隐藏在临时客户端编码器中,但不会阻止编码人员进行更严肃的工作。
答案 7 :(得分:1)
根据问题的解释,有两个答案。
第一种方法是从接口隐藏方法实现。这是使用的,通常使用没有名称的类别(例如@interface Foo()
)。这允许对象发送这些消息而不发送其他消息 - 尽管可能仍会意外地(或其他方式)覆盖。
第二个答案,假设这是关于性能和内联,是可能的,但作为一个本地C函数。如果你想要一个'私人foo(NSString *arg
)'方法,你可以void MyClass_foo(MyClass *self, NSString *arg)
并将其称为C函数,如MyClass_foo(self,arg)
。语法不同,但它具有C ++私有方法的理智的性能特征。
虽然这回答了这个问题,但我应该指出,无名类别是目前比较常见的Objective-C方式。
答案 8 :(得分:0)
Objective-C不支持私有方法,因为它不需要它们。
在C ++中,每个方法都必须在类的声明中可见。您不能拥有包含头文件的人无法看到的方法。因此,如果您希望实现之外的代码不应该使用的方法,您别无选择,编译器必须为您提供一些工具,以便您可以告诉它不能使用该方法,即“private”关键字。
在Objective-C中,您可以拥有不在头文件中的方法。因此,通过不将方法添加到头文件中,您可以非常轻松地实现相同的目的。没有私人方法。 Objective-C还具有以下优点:您不需要重新编译类的每个用户,因为您更改了私有方法。
例如,您曾经必须在头文件中声明的变量(不再是),@ private,@ public和@protected可用。
答案 9 :(得分:0)
这里缺少的答案是:因为从可演化的角度来看私有方法是个坏主意。在编写方法时将方法设为私有似乎是个好主意,但它是早期绑定的一种形式。上下文可能会更改,以后的用户可能希望使用其他实现。有点挑衅:“敏捷开发人员不使用私有方法”
在某种程度上,就像Smalltalk一样,Objective-C适用于成年程序员。我们非常重视原始开发人员认为接口应该是什么,并且如果我们需要更改实现,则负责处理后果。是的,这是哲学,而不是实施。