我已经看到了一些在Objective-C中声明半私有方法的策略,但似乎没有办法制作一个真正的私有方法.我接受.但是,为什么会这样呢?我基本上所说的每一个解释,"你不能这样做,但这里有一个近似的近似."
有许多适用于关键字的ivars
(部件)控制它们的范围,例如@private
,@public
,@protected
.为什么不能这样做呢?它似乎是运行时应该能够支持的东西.我缺少一种潜在的哲学吗?这是故意的吗?
答案是......好吧......简单.事实上,简单性和一致性.
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的私有方法,但它足以有效地接近,所以为什么改变语言,以及编译器,运行时间等的语义,添加一个已经在可接受模拟功能办法?正如其他答案所述,消息传递语义 - 以及它们对运行时反射的依赖 - 将使处理"私有"消息变得非常重要.
最简单的解决方案是在Objective-C类中声明一些静态C函数.这些只有静态关键字的C规则的文件范围,因此它们只能由该类中的方法使用.
不用大惊小怪.
是的,它可以通过利用编译器已经采用的技术来处理C++而不影响运行时完成:名称修改.
尚未完成,因为尚未确定它将解决编码问题空间中的一些相当大的困难,其他技术(例如,前缀或下划线)能够充分规避.IOW,你需要更多的痛苦来克服根深蒂固的习惯.
您可以为clang或gcc提供补丁,为语法添加私有方法,并生成在编译过程中单独识别的错误名称(并立即忘记).然后,Objective-C社区中的其他人将能够确定它是否真的值得.它可能比试图说服开发者更快.