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