Objective-C的隐藏特性

12

由于苹果公司在Mac OS X和iPhone开发中使用Objective-C,因此该语言的使用范围正在扩大。你最喜欢的Objective-C语言的"隐藏"特性是什么?

  • 每个回答只涉及一个特性。
  • 给出特性的示例和简短描述,不要只提供文档链接。
  • 使用标题作为第一行标记特性。

最初的回答:

我最喜欢的Objective-C语言的"隐藏"特性是类别(Category)。

类别允许您将方法添加到现有的类中,而无需创建子类。这非常有用,因为它使您可以将代码分散到多个文件中,并且可以更好地组织您的代码库。例如,您可以将所有字符串处理方法放在一个名为NSString+Utils的类别中,然后在整个应用程序中重复使用它们。


请参阅http://meta.stackexchange.com/questions/56669/should-hidden-features-of-x-be-removed-closed-locked,http://meta.stackexchange.com/questions/57226/should-we-have-a-list-of-x-close-reason以及相关的Meta帖子。 - Roger Pate
8个回答

20

方法交换

基本上,在运行时,您可以将一个方法的实现与另一个方法交换。

这里有一篇带有代码的解释。

一个聪明的用例是用于共享资源的惰性加载:通常情况下,您会通过获取锁、创建 foo(如果需要),获取其地址、释放锁,然后返回foo来实现 sharedFoo 方法。这确保了 foo 只被创建一次,但是每个后续访问都浪费了时间,因为锁不再需要。

使用方法交换,您可以像以前一样做,除了在 foo 被创建后,使用交换来替换 sharedFoo 的初始实现为第二个实现,它不检查任何内容并简单地返回我们现在已知已创建的 foo

当然,方法交换可能会让您陷入麻烦,并且可能存在以上示例不适用的情况,但是嘿……这就是为什么它是一个“隐藏”的功能。


16

替身技术

在Objective-C中,一个类可以完全替换应用程序中的另一个类。替换的类被称为“pose as”目标类。所有发送给目标类的消息都将被代替类接收。关于哪些类可以进行替身有一些限制:

  • 一个类只能替身其直接或间接的父类之一。
  • 替身类不得定义任何在目标类中不存在的新实例变量(但可以定义或覆盖方法)。
  • 在替身之前不能向目标类发送任何消息。

替身技术和分类一样,允许全局增强现有的类。替身技术允许两个分类缺失的特性:

  • 替身类可以通过super调用重写的方法,从而合并目标类的实现。
  • 替身类可以覆盖在分类中定义的方法。

举个例子:

@interface CustomNSApplication : NSApplication
@end

@implementation CustomNSApplication
- (void) setMainMenu: (NSMenu*) menu
{
     // do something with menu
}
@end

class_poseAs ([CustomNSApplication class], [NSApplication class]);

这拦截了对NSApplication的每个setMainMenu调用。


4
Class_poseAs方法从10.5开始已经被弃用。现在在运行时明确支持的替代方式是逐个方法地执行相同的操作。请注意,只进行翻译,不提供任何解释或其他内容。 - Ken
哎呀,忘记链接文档了。 - Ken

15

对象转发/方法缺失

当一个对象接收到一个它没有实现的消息时,运行时系统会在放弃前给它另外一次处理的机会。如果该对象支持一个 -forward:: 方法,运行时系统会调用这个方法,并传递有关未处理调用的信息。从转发的调用返回值将向原始调用者传播。

-(retval_t)forward:(SEL)sel :(arglist_t)args {
  if ([myDelegate respondsTo:sel])
 return [myDelegate performv:sel :args]
 else
 return [super forward:sel :args];
 }

来自Objective-C Pocket Reference的内容。

这非常强大,在Ruby社区中被广泛应用于各种DSL和Rails等。起源于Smalltalk,影响了Objective-C和Ruby。


4
我建议使用已记录的方法-[NSObject forwardInvocation:]。未记录的方法-[Object forward::]已经过时很久了,在现代Objective-C运行时中不再可用,例如在iOS设备上的64位和ARM。 - PeyloW
有人知道这个还能用吗?我似乎找不到任何关于它的文档。 - Richard J. Ross III

11

ISA切换

需要覆盖对象的所有行为?您实际上可以通过一行代码更改活动对象的类:

obj->isa = [NewClass class];
这只改变了接收该对象方法调用的类;它并不会改变对象在内存中的布局。因此,只有当您有一组具有相同ivars(或一个子集)的类,并且您想在它们之间切换时,这才真正有用。

我编写的一段代码使用它进行延迟加载:它分配了一个类A的对象,填充了几个关键的ivars(在这种情况下,主要是记录编号),并将isa指针切换到LazyA。 当调用除了像release和retain这样的一小组方法之外的任何方法时,LazyA会从磁盘加载所有数据,完成填充ivars,将isa指针切换回A,并将调用转发到真实的类。


1
我的天啊,我从未想过那一点。我需要测试一下,因为这太妙了。不知道是否会有任何不良反应? - Jonathan Sterling
2
@MattDiPasquale 最佳答案是正确的 - 现代的方法是使用objc_setClass()。 - Becca Royal-Gordon

10

分类

使用分类,您可以在不需要子类化的情况下向内置类添加方法。 详细参考

将便利方法添加到常用类如NSString或NSData中,十分实用。


3
好的,但并不完全隐藏... :) - Jonathan Sterling

10
#include <Foundation/Debug.h>

在这个头文件中,有很多工具可以尝试跟踪内存泄漏、过早释放等问题。


1
这是Cocoa特定的(也许还包括OpenSTEP?),而不是一种语言特性,尽管由于除了与Cocoa一起使用之外没有人使用ObjC,所以我猜这并没有太大的区别。 - Mark Baker
那么,这对Cocoa Touch,即iOS无效吗?它确切地做了什么? - ma11hew28

4

Objective-C Runtime参考

很容易忘记Objective-C的语法糖被转换为普通的C函数调用,这就是Objective-C Runtime。你可能永远不需要深入并使用运行时系统中的任何内容。这就是为什么我认为这是一个“隐藏功能”。

让我来介绍一下如何使用运行时系统。

假设有人正在设计一个外部框架API,将由第三方使用。那个人在框架中设计了一个类,抽象地表示数据包,我们称之为MLAbstractDataPacket。现在,链接框架的应用程序必须子类化MLAbstractDataPacket并定义子类数据包。每个子类都必须重写方法+(BOOL)isMyKindOfDataPacket:(NSData *)data

在此基础上...

如果MLAbstractDataPacket提供了一个便利方法,返回以+(id)initWithDataPacket:(NSData *)data形式出现的数据包的正确初始化类,那就太好了。

这里只有一个问题。超类不知道其任何子类。因此,您可以使用运行时方法objc_getClassList()objc_getSuperclass()查找MLAbstractDataPacket的子类。一旦您获得了子类列表,您就可以尝试在每个子类上使用+isMyKindOfDataPacket:,直到找到或未找到为止。

关于此的参考信息可以在http://developer.apple.com/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html找到。


0

我喜欢冗长的方法命名方式,比如[myArray writeToFile:myPath atomically:YES],每个参数都有标签。


9
那是怎么隐藏的?... :) - Kuba Suder
1
@Psionides:对于实际使用该语言的人来说,这一点非常明显,我同意。但是大多数从其他语言转换过来的人似乎都忽略了这个简单的事实,至少我和他们交流时是这样的。 - Kris
4
你不能错过它!如果你真的想传递信息的话。 - Casebash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接