为什么Objective-C不支持私有方法?


123

我已经看到了许多在Objective-C中声明半私有方法的策略,但是似乎没有一种方法可以实现真正的私有方法。我接受。但是,为什么会这样呢?我基本上说过的每一种解释都是:“你做不到,但这是一个近似值。”

有许多关键字适用于 ivars的是控制它们的范围内,例如(成员)@private@public@protected。为什么不能同时为方法完成此操作?似乎运行时应该能够支持。我是否缺少基本的哲学?这是故意的吗?


15
好问题,顺便说一句。
bbum 2010年

5
对您的编辑:是的,运行时实施将非常昂贵,几乎没有收益。编译时执行将是该语言的一个受欢迎的补充,并且是完全可以实现的。是的,可以在运行时规避它,但这很好。程序员不是敌人。作用域的目的是帮助保护程序员免于错误。
罗布·纳皮尔

1
您不能“跳过运行时” —运行时是消息分派的工作。
查克(Chuck)2010年

“没有任何一种私人方法可以使这张支票短路。您的意思是“规避”吗?;-)
Constantino Tsarouhas 2012年

Answers:


103

答案很简单。实际上,简单性和一致性。

在方法派发时,Objective-C纯粹是动态的。特别是,每个方法分派都要经过与其他所有方法分派完全相同的动态方法解析点。在运行时,每个方法实现都具有完全相同的公开性,并且由Objective-C运行时提供的与方法一起使用的所有API和选择器在所有方法上均相同地工作。

正如许多人回答的(在这里和在其他问题中),都支持编译时专用方法。如果一个类没有在其公共可用接口中声明一个方法,那么就您的代码而言,该方法也可能不存在。换句话说,通过适当地组织项目,可以在编译时实现所需的可见性的所有各种组合。

将相同功能复制到运行时几乎没有好处。这将增加大量的复杂性和开销。即使存在所有这些复杂性,它也不会阻止除最随意的开发人员以外的所有人员执行您所谓的“私有”方法。

编辑:我注意到的一个假设是私人消息将必须通过运行时,从而导致潜在的大开销。这是真的吗?

是的。没有理由假设类的实现者不希望使用实现中的所有Objective-C功能集,这意味着必须进行动态分配。 但是,没有特殊的原因为什么不能通过的特殊变体来调度私有方法objc_msgSend(),因为编译器会知道它们是私有的。即,可以通过在Class结构中添加专用方法表来实现。

私有方法没有办法缩短此检查或跳过运行时吗?

它无法跳过运行时,但运行时不会一定要做到为私有方法的任何检查。

就是说,没有理由第三方无法objc_msgSendPrivate()在该对象的实现之外故意调用该对象,而某些事情(例如KVO)则必须这样做。实际上,这只是一个约定,实际上比在私有方法的选择器前添加前缀或在接口头中不提及它们要好。

但是,这样做会破坏语言的纯粹动态特性。不再将每个方法的调度都通过相同的调度机制。取而代之的是,您将处于大多数方法仅表现为一种方法而少数方法却有所不同的情况下。

这超出了运行时,因为Cocoa中有许多机制是建立在Objective-C一致的动态性之上的。例如,密钥值编码和密钥值观察都必须进行大量修改以支持私有方法(最有可能通过创建可利用的漏洞),否则私有方法将不兼容。


49
+1 Objective-C是(从某种意义上来说)一种“大佬”语言,期望程序员表现出一定的纪律,而不是动every动脑地被编译器/运行时打败。我觉得很爽。对我来说,在编译/链接期间限制方法的可见性已足够并且是可取的。
奎因·泰勒

11
更多的python是“ obj-c-ic” :)。Guido非常积极地在NeXT系统上维护Python,包括创建PyObjC的第一个版本。因此,ObjC确实对python 有一定影响。
bbum 2010年

3
+1是关于生命中仁慈的独裁者的有趣历史故事,以及他与神话般的NeXT系统的杂交。
pokstad 2010年

5
谢谢。Python,Java和Objective-C都具有相当类似于[SmallTalk]的运行时对象模型。Java是直接从Objective-C派生的。Python不仅具有启发性,而且还具有并行性,但是Guido当然可以仔细研究NeX​​TSTEP。
bbum 2010年

1
除了我不信任它的许可证,我绝对会喜欢clang而不是gcc。但这无疑是一个很好的第一步。
Cthutu 2012年

18

运行时可以支持它,但代价是巨大的。发送的每个选择器都需要检查该类是私有的还是公共的,或者每个类都需要管理两个单独的调度表。对于实例变量,这是不一样的,因为这种保护级别是在编译时完成的。

同样,运行时将需要验证私人消息的发送者与接收者属于同一类。您也可以绕过私有方法。如果使用了该类instanceMethodForSelector:,则可以将返回的值提供IMP给其他任何类,以便它们直接调用私有方法。

专用方法无法绕过消息分发。请考虑以下情形:

  1. AllPublic具有公共实例方法doSomething

  2. 另一个类HasPrivate有一个私有实例方法,也称为doSomething

  3. 您创建一个包含任意数量的两个实例的数组AllPublicHasPrivate

  4. 您有以下循环:

    for (id anObject in myArray)
        [anObject doSomething];

    如果你从内跑了循环AllPublic,运行时间将不得不停止在发送doSomethingHasPrivate情况,但是这循环将是可用的,如果它是内部HasPrivate类。


1
我同意会涉及费用。也许这不是您在手持设备上想要的东西。否则您可以支持巨大的资格赛吗?在台式机系统上,成本真的很重要吗?
罗伯·琼斯

1
虽然没有,但您可能是对的。Objective-C具有运行时消息分发功能,而不是C ++的编译时方法调用,因此您将谈论编译时检查与运行时检查。
Remus Rusanu 2010年

不支持私有方法的主要原因是由于性能,这真是很奇怪。我认为苹果公司根本不认为私有方法是非常必要的,无论是否对性能产生了影响。否则,他们应该为拥有数十亿美元资产的公司选择其他语言。
pokstad,2010年

1
添加私有方法不仅会使运行时的实现复杂化,还会使语言的语义复杂化。访问控制是静态语言中的一个简单概念,但与动态分派不匹配,因为动态分派会带来很多概念上的开销,并导致很多“为什么这行不通?” 问题。
詹斯·艾顿

7
私有方法真的会给您带来什么?毕竟,Objective-C是基于C的语言。因此,如果某人在您的流程中运行了代码,那么他们可以轻松地将您的流程设置为p0wnz,而不管任何API的私有性或混淆性如何。
bbum 2010年

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的私有方法,但它有效的足够接近,那么为什么改变语言的语义,以及编译器,运行时间等,以补充一个已经在可接受模拟功能方式?正如在其他答案中指出的那样,消息传递语义-以及它们对运行时反射的依赖-将使处理“私有”消息变得不平凡。


1
这种方法的问题在于,当有人将您的类子类化时,他们可以重写私有方法(即使在不知不觉中)。我想,基本上,您必须选择不太可能被覆盖的名称。
dreamlax

3
在我看来,这就像是骇客。
罗伯·琼斯

1
@Mike:苹果公司已经声明,带下划线的方法名称仅专门为苹果公司保留:developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…。推荐的替代方法是在前缀两个大写字母后加一个下划线。
dreamlax 2010年

8
另外,在该方法后面加上SSN,以便其他程序员知道是谁编写的。= D命名空间,需要它们!!!
pokstad,2010年

2
如果您担心定义的方法会被覆盖,则说明您没有正确接受Objective-C鼓励的固有冗长级别…
Kendall Helmstetter Gelner 2010年

7

最简单的解决方案是在Objective-C类中声明一些静态C函数。根据static关键字的C规则,它们仅具有文件作用域,因此它们只能由该类中的方法使用。

不用大惊小怪。


这些必须在@implementation部分之外定义吗?
罗伯·琼斯

@Rob-不可以(并且很可能应该在类实现中定义它们)。
Cromulent

6

是的,可以通过利用编译器已经使用的用于处理C ++的技术来完成操作,而不会影响运行时:名称处理。

尚未完成,因为尚未确定它将解决编码技术领域中其他技术(例如前缀或下划线)能够充分规避的相当大的困难。IOW,您需要更多的痛苦来克服根深蒂固的习惯。

您可以为clang或gcc贡献补丁,以将专用方法添加到语法中,并生成在编译过程中被单独识别(并立即忘记)的损坏的名称。然后,Objective-C社区中的其他人将能够确定它是否真正值得。这样可能比尝试说服开发人员更快。


2
编译时名称修改比一个人想象的要难得多,因为您必须修改所有方法选择器,包括那些可能出现在XIB文件中的选择器以及那些可能传递给诸如NSSelectorFromString()以及KeyValueCoding和好友之类的方法的选择器……
bbum 2010年

1
是的,如果您想为整个工具链中的私有方法提供支持,它将涉及更多。编译器的符号表将需要存储,并可以通过某些工具链api访问。在时间和稳定性允许的情况下,这并不是苹果第一次向开发人员逐步推出对开发者至关重要的功能。但是,在涉及私有方法的情况下:进行这项工作是否足够成功值得怀疑。甚至更怀疑这些其他机制是否应该在类本身之外访问它们。
Huperniketes

1
仅编译时强制+名称处理将如何阻止某人浏览类的方法列表并在其中找到它?
dreamlax

不会的。我的回答仅解决了OP提出的问题。
10年

我曾经考虑过尝试向Clang添加私有方法,我认为私有方法会很好的一个原因是,可以像简单的c函数一样直接调用私有方法,然后可以进行所有常用的C函数优化。我从来没有因为时间而烦恼,您已经可以使用c做到这一点。
弥敦道日

4

本质上,它与Objective-C的方法调用的消息传递形式有关。任何消息都可以发送到任何对象,并且对象选择如何响应消息。通常,它会通过执行以消息命名的方法来响应,但是它也可以通过许多其他方式来响应。这并没有使私有方法完全不可能-Ruby在类似的消息传递系统中做到了-但是确实使它们有些尴尬。

由于奇怪,甚至Ruby的私有方法实现也使人感到困惑(除了此列表中的消息,您可以向对象发送任何您喜欢的消息!)。本质上,Ruby通过禁止使用显式接收器调用的私有方法来使其工作。在Objective-C中,这将需要更多的工作,因为Objective-C没有该选项。


1

Objective-C的运行时环境存在问题。尽管C / C ++可以编译为不可读的机器代码,但是Objective-C仍然保留了一些人类可读的属性,例如方法名称为string。这使Objective-C能够执行反射功能。

编辑:没有严格的私有方法的反射型语言使Objective-C更具“ Python风格”,因为您信任使用您代码的其他人,而不是限制他们可以调用的方法。使用双下划线之类的命名约定旨在使您的代码对普通的客户端编码器隐藏,但不会阻止编码器需要做更认真的工作。


如果您的lib的使用者可以反映您的私有方法,他们也不能称呼它们吗?
Jared Updike 2010年

如果他们知道在哪里看,那不是人们一直在使用iPhone上未记录的库吗?iPhone的问题在于,您冒着不接受使用任何私有API的应用程序的风险。
pokstad,2010年

如果他们通过反思访问私有方法,他们将得到应有的报酬。任何错误都不是您的错。
gnasher729 2014年

1

根据问题的解释,有两个答案。

首先是通过从接口隐藏方法实现。通常使用不带名称的类别(例如@interface Foo())。这允许对象发送那些消息,但不能发送其他消息-尽管一个消息仍可能会意外覆盖(或其他方式)。

假设这是关于性能和内联的,则第二个答案成为可能,但可以使用本地C函数。如果您想要一个“私有foo(NSString *arg)”方法,则可以将void MyClass_foo(MyClass *self, NSString *arg)其作为C函数调用,例如MyClass_foo(self,arg)。语法是不同的,但是它具有C ++私有方法的理智的性能特征。

尽管这回答了问题,但我应该指出,到目前为止,无名称类别是最常见的Objective-C方法。


0

Objective-C不支持私有方法,因为它不需要它们。

在C ++中,每个方法都必须在类的声明中可见。您不能拥有某些人(包括头文件)无法看到的方法。因此,如果希望不要使用实现之外的代码的方法,则别无选择,编译器必须为您提供一些工具,以便您可以告诉它不得使用该方法,即“ private”关键字。

在Objective-C中,您可以具有不在头文件中的方法。因此,通过不将方法添加到头文件中,可以非常轻松地实现相同的目的。不需要私有方法。Objective-C还具有不需要更改类的每个用户的优点,因为您更改了私有方法。

例如,您以前必须在头文件中声明的实例变量(现在不再),可以使用@ private,@ public和@protected。


0

这里缺少一个答案:因为从可扩展性的角度来看,私有方法不是一个好主意。在编写方法时将方法设为私有似乎是个好主意,但这是早期绑定的一种形式。上下文可能会更改,以后的用户可能想使用其他实现。有点挑衅:“敏捷开发人员不使用私有方法”

就像Smalltalk一样,Objective-C在某种程度上也适合成年的程序员。我们很重视知道原始开发人员假定接口应该是什么,并在需要更改实现时承担处理后果的责任。是的,这是哲学,而不是实施。


我对此进行了投票,因为它不值得接受。正如Stephan所说,有一个论点是“私有”方法不是必需的,它与关于动态类型和静态类型的论点相似,因为无论您的首选结论如何,双方都有观点。
alastair 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.