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