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

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

有许多应用于 ivars(成员)的关键字控制它们的范围,例如 @private@public@protected。为什么不能对方法也这样做呢?这似乎是运行时应该能够支持的东西。我是不是遗漏了什么基本原理?这是故意的吗?

24048 次浏览

这是 Objective-C 执行期函式库的问题。将 而 C/C + + 编译成不可读的机器代码 Objective-C 仍然维护一些人类可读的属性,比如作为字符串的方法名。这使 Objective-C 能够执行 reflective特性。

编辑: 作为一种没有严格的私有方法的反射性语言,Objective-C 更加“ pythonic”,因为你信任使用你的代码的其他人,而不是限制他们可以调用什么方法。使用像双下划线这样的命名约定是为了向临时客户端程序员隐藏您的代码,但这并不能阻止程序员需要做更多严肃的工作。

The runtime could support it but the cost would be enormous. Every selector that is sent would need to be checked for whether it is private or public for that class, or each class would need to manage two separate dispatch tables. This isn't the same for instance variables because this level of protection is done at compile time.

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

私有方法无法绕过消息分派:

  1. AllPublic有一个公共实例方法 doSomething

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

  3. 创建一个同时包含 AllPublicHasPrivate的任何数量的数组

  4. 您有以下循环:

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

    If you ran that loop from within AllPublic, the runtime would have to stop you sending doSomething on the HasPrivate instances, however this loop would be usable if it was inside the HasPrivate class.

本质上,它与 Objective-C 的方法调用的消息传递形式有关。任何消息都可以发送到任何对象,对象选择如何响应该消息。通常,它将通过执行以消息命名的方法进行响应,但是它也可以通过许多其他方式进行响应。这并不意味着私有方法完全不可能实现ーー Ruby 使用类似的消息传递系统实现了这一点ーー但这确实让私有方法变得有些笨拙。

甚至 Ruby 的私有方法的实现也有点让人困惑,因为它很奇怪(你可以发送任何你喜欢的消息给对象,除了这张单子上的!).从本质上讲,Ruby 通过禁止私有方法与显式接收方一起调用来使其工作。在 Objective-C 中需要做更多的工作,因为 Objective-C 没有这个选项。

到目前为止,从哲学的角度来看,公布的答案很好地回答了这个问题,所以我要假设一个更实际的原因: 改变语言的语义会得到什么?有效地“隐藏”私有方法非常简单。例如,假设您有一个在头文件中声明的类,如下所示:

@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 在方法分派时是纯动态的。特别是,每个方法分派都经历与其他方法分派完全相同的动态方法解析点。在运行时,每个方法实现都有完全相同的公开,并且 Objective-C 运行时提供的所有与方法和选择器一起工作的 API 在所有方法中都是相同的。

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

将相同的功能复制到运行时中几乎没有什么好处。它将增加大量的复杂性和开销。即使有这么多的复杂性,它仍然不能阻止除了最随意的开发人员之外的所有人执行所谓的“私有”方法。

编辑: 我的一个假设是 注意到私人信息会 必须经过运行时 导致潜在的大规模 这是真的吗?

是的,没错。没有理由假设类的实现者不想使用实现中的所有 Objective-C 特性集,这意味着克劳斯·福尔曼必须发生。因为编译器知道私有方法是私有的,所以没有特别的理由不能通过 objc_msgSend()的特殊变体来调度私有方法; 也就是说,这可以通过在 Class结构中添加一个只有私有的方法表来实现。

没有办法让二等兵 method to short-circuit this check or 跳过运行时?

它不能跳过运行时,但是运行时 不会必须对私有方法进行任何检查。

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

然而,这样做会破坏语言纯粹的动态本质。不再需要每个方法分派都通过相同的分派机制。相反,您将处于这样一种情况,即大多数方法只有一种行为方式,而少数方法的行为方式只是不同。

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

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

第一种方法是从接口中隐藏方法实现。使用这种方法,通常使用没有名称的类别(例如 @interface Foo())。这允许对象发送这些消息,但不能发送其他消息——尽管仍然可能意外(或以其他方式)覆盖。

第二个答案,假设这是关于性能和内联,是可能的,但作为一个本地 C 函数代替。如果需要一个“私有 foo (NSString *arg)”方法,那么可以执行 void MyClass_foo(MyClass *self, NSString *arg)并将其作为一个类似于 MyClass_foo(self,arg)的 C 函数来调用。语法是不同的,但是它与 C + + 的私有方法的性能特征相一致。

虽然这回答了这个问题,但是我应该指出,到目前为止,没有名称的类别是更常见的 Objective-C 方法。

是的,通过使用编译器已经使用的处理 C + + 的技术: 名称错位,可以在不影响运行时的情况下完成。

之所以没有这样做,是因为还没有确定它能够解决编码问题空间中其他技术(例如,前缀或下划线)能够充分规避的一些相当大的困难。你需要更多的痛苦来克服根深蒂固的习惯。

您可以为 clang 或 gcc 提供补丁,这些补丁向语法中添加私有方法,并生成它在编译期间单独识别(并迅速忘记)的混乱名称。然后 Objective-C 社区中的其他人将能够确定它实际上是否值得。这种方式可能比试图说服开发人员更快。

The easiest solution is just to declare some static C functions in your Objective-C classes. These only have file scope as per the C rules for the static keyword and because of that they can only be used by methods in that class.

没什么大惊小怪的。

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

在 C + + 中,每个方法都必须在类的声明中可见。不能有包含头文件的人看不到的方法。因此,如果你想要方法,代码外的实现不应该使用,你没有选择,编译器必须给你一些工具,这样你就可以告诉它,方法不能使用,这是“私有”关键字。

In Objective-C, you can have methods that are not in the header file. So you achieve the same purpose very easily by not adding the method to the header file. There's no need for private methods. Objective-C also has the advantage that you don't need to recompile every user of a class because you changed private methods.

例如,可以使用以前必须在头文件中声明的变量@private、@public 和@protected。

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

在某种程度上,就像 Smalltalk 一样,Objective-C 是为成年程序员准备的。我们重视了解最初的开发人员假定接口应该是什么样的,并且在需要更改实现时负责处理后果。所以,是的,这是哲学,而不是执行。