我已经看到了许多在Objective-C中声明半私有方法的策略,但是似乎没有一种方法可以实现真正的私有方法。我接受。但是,为什么会这样呢?我基本上说过的每一种解释都是:“你做不到,但这是一个近似值。”
有许多关键字适用于 ivars
的是控制它们的范围内,例如(成员)@private
,@public
,@protected
。为什么不能同时为方法完成此操作?似乎运行时应该能够支持。我是否缺少基本的哲学?这是故意的吗?
我已经看到了许多在Objective-C中声明半私有方法的策略,但是似乎没有一种方法可以实现真正的私有方法。我接受。但是,为什么会这样呢?我基本上说过的每一种解释都是:“你做不到,但这是一个近似值。”
有许多关键字适用于 ivars
的是控制它们的范围内,例如(成员)@private
,@public
,@protected
。为什么不能同时为方法完成此操作?似乎运行时应该能够支持。我是否缺少基本的哲学?这是故意的吗?
Answers:
答案很简单。实际上,简单性和一致性。
在方法派发时,Objective-C纯粹是动态的。特别是,每个方法分派都要经过与其他所有方法分派完全相同的动态方法解析点。在运行时,每个方法实现都具有完全相同的公开性,并且由Objective-C运行时提供的与方法一起使用的所有API和选择器在所有方法上均相同地工作。
正如许多人回答的(在这里和在其他问题中),都支持编译时专用方法。如果一个类没有在其公共可用接口中声明一个方法,那么就您的代码而言,该方法也可能不存在。换句话说,通过适当地组织项目,可以在编译时实现所需的可见性的所有各种组合。
将相同功能复制到运行时几乎没有好处。这将增加大量的复杂性和开销。即使存在所有这些复杂性,它也不会阻止除最随意的开发人员以外的所有人员执行您所谓的“私有”方法。
编辑:我注意到的一个假设是私人消息将必须通过运行时,从而导致潜在的大开销。这是真的吗?
是的。没有理由假设类的实现者不希望使用实现中的所有Objective-C功能集,这意味着必须进行动态分配。 但是,没有特殊的原因为什么不能通过的特殊变体来调度私有方法
objc_msgSend()
,因为编译器会知道它们是私有的。即,可以通过在Class
结构中添加专用方法表来实现。私有方法没有办法缩短此检查或跳过运行时吗?
它无法跳过运行时,但运行时不会一定要做到为私有方法的任何检查。
就是说,没有理由第三方无法
objc_msgSendPrivate()
在该对象的实现之外故意调用该对象,而某些事情(例如KVO)则必须这样做。实际上,这只是一个约定,实际上比在私有方法的选择器前添加前缀或在接口头中不提及它们要好。
但是,这样做会破坏语言的纯粹动态特性。不再将每个方法的调度都通过相同的调度机制。取而代之的是,您将处于大多数方法仅表现为一种方法而少数方法却有所不同的情况下。
这超出了运行时,因为Cocoa中有许多机制是建立在Objective-C一致的动态性之上的。例如,密钥值编码和密钥值观察都必须进行大量修改以支持私有方法(最有可能通过创建可利用的漏洞),否则私有方法将不兼容。
运行时可以支持它,但代价是巨大的。发送的每个选择器都需要检查该类是私有的还是公共的,或者每个类都需要管理两个单独的调度表。对于实例变量,这是不一样的,因为这种保护级别是在编译时完成的。
同样,运行时将需要验证私人消息的发送者与接收者属于同一类。您也可以绕过私有方法。如果使用了该类instanceMethodForSelector:
,则可以将返回的值提供IMP
给其他任何类,以便它们直接调用私有方法。
专用方法无法绕过消息分发。请考虑以下情形:
类AllPublic
具有公共实例方法doSomething
另一个类HasPrivate
有一个私有实例方法,也称为doSomething
您创建一个包含任意数量的两个实例的数组AllPublic
和HasPrivate
您有以下循环:
for (id anObject in myArray)
[anObject doSomething];
如果你从内跑了循环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的私有方法,但它有效的足够接近,那么为什么改变语言的语义,以及编译器,运行时间等,以补充一个已经在可接受模拟功能方式?正如在其他答案中指出的那样,消息传递语义-以及它们对运行时反射的依赖-将使处理“私有”消息变得不平凡。
是的,可以通过利用编译器已经使用的用于处理C ++的技术来完成操作,而不会影响运行时:名称处理。
尚未完成,因为尚未确定它将解决编码技术领域中其他技术(例如前缀或下划线)能够充分规避的相当大的困难。IOW,您需要更多的痛苦来克服根深蒂固的习惯。
您可以为clang或gcc贡献补丁,以将专用方法添加到语法中,并生成在编译过程中被单独识别(并立即忘记)的损坏的名称。然后,Objective-C社区中的其他人将能够确定它是否真正值得。这样可能比尝试说服开发人员更快。
Objective-C的运行时环境存在问题。尽管C / C ++可以编译为不可读的机器代码,但是Objective-C仍然保留了一些人类可读的属性,例如方法名称为string。这使Objective-C能够执行反射功能。
编辑:没有严格的私有方法的反射型语言使Objective-C更具“ Python风格”,因为您信任使用您代码的其他人,而不是限制他们可以调用的方法。使用双下划线之类的命名约定旨在使您的代码对普通的客户端编码器隐藏,但不会阻止编码器需要做更认真的工作。
根据问题的解释,有两个答案。
首先是通过从接口隐藏方法实现。通常使用不带名称的类别(例如@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在某种程度上也适合成年的程序员。我们很重视知道原始开发人员假定接口应该是什么,并在需要更改实现时承担处理后果的责任。是的,这是哲学,而不是实施。