为C++编程者开发的Objective-C(三)

来源:互联网 发布:360数据恢复在哪里 编辑:程序博客网 时间:2024/05/16 13:46

David Chisnall以探索Objective-C语言更多的一些高级概念来总结他的三部分。在这个系列的第一、第二部分,我们看到了Objective-C语言的核心,定义类、创建对象和给他们传送消息。最后一部分涵盖了这个语言的更多高级部分。

 

内省

C++通过运行时类型信息(RTTI)支持内省。虽然是通过编译器不完全支持;甚至在支持的地方也不会真正的用到,是基于性能原因。相反,内省信息在Objective-C中是可获得的。

 

类结构包含一个链表或关于方法和其他实例变量的元数据数组。每一个实例变量包含从对象起始得到的偏移、类型、名字。你可以通过这个设置做一些有趣的事。举例来说,我曾经编写一个étoilé的框架来回顾这些信息并用它们来自动序列化一些对象(仅在C++下对那些你有源代码的对象)。对于方法,它有名字,类型和实现方法的函数指针。这是一个实例方法指针,并且在头部如下定义:

 

Typedef id(*IMP)(id,SEL,…);这里使用了其它两种Objective-C类型。id类型是指向某种对象的指针。所有你对id的了解就是它来回应消息(虽然你不知道是什么消息,除非你询问一下)。另一种类型就是SEL,一种选择器类型。

 

你可以这样做:

IMP method = [object methodForSelector:@selector(doSomething)];变量方法是一个C函数指针指向方法的实现。如果你发送相同的消息给对象,你可以通过这种性能获得额外的速度。

 

因为你可以在运行时构造选择器,你可以对一些动态行为使用这种方式。在XML语法分析器中,当对对象封装一个XML对象的子元素时我使用这种方式。父类实现-addChild:forKey:方法包含下列代码。

 

NSString * childSelectorName = [NSString stringWithFormat:@"add%@:", aKey];

SEL childSelector = NSSelectorFromString(childSelectorName);

if([self respondsToSelector:childSelector])

{

  [self performSelector:childSelector withObject:aChild];

}这从键名中构建了一个选择器并且和对象一起调用。这种技术被用在XMPP实现。举例来说,<name>标签可以通过一个类来分析,这个类收集字符数据并返回一个字符串。当收到</name>,它操作如下:

 

[parent addChild:string forKey:@"name"]; parent运行这个方法,调用-addname:带字符串的方法作为一个参数。更复杂的格式在Cocoa的键-值代码中实现,这内省了方法和实例变量的元数据。当你调用-setValue:forKey:它将调用一个setter,直接设置了一个实例变量,或者调用-setValueforUndefineKey:。使用键-值代码比设置实例变量或调用set/get方法速度要慢,但是可以完全从接口的实现中分离出来。

 

随着Objective-C2.0的出现,Apple引进了属性、存储这种方式:

object.property = 12;这是语法糖的非常薄的一层。内部来说,他们翻译成setget是基于属性的使用。属性最好的特性就是他们允许setget方法由任何一个给出的实例变量自动产生,随着retainassigncopy语法。

 

协议

Objective-C中,协议是类实现消息的集。你可以具体化指针指向类实现给定的接口。

 

id<AnInterface> object;

NSString<AnInterface> *string;第一个例子和在Java中声明变量作为接口类型是等价的。在C++中最接近的等价就是使用抽象类取代接口并具体制定抽象类为类型。

 

第二个例子更有趣。字符串变量被允许成为任何NSString的子类并实现AnInterface,这允许你限制一种实现特定接口的子类子集的类型。一个通常的例子如下:

 

NSObject<MyDelegateProtocol> *delegate;这允许成为任何NSObject的子类(并且你期望从任何对象得到的方法都会实现)并且要求它实现特殊的协议。还有一个可供选择的:

 

id <NSObject, MyDelegateProtocol> delegate;这作用于NSObject,因为随着类采取协议,NSObject既是一个类也是一个协议。NSProxy就是这样做的,和其他一些父类可以被间或的用作NSObject的子类。

 

因为Objective-C来自于Smalltalk,随着任何都是对象,这并不是一个惊人的协议,象类也是对象。因此,你可以对他们发送消息来执行内省。典型地来说,这些都不必自己做,而是依赖于消息发给NSObject的子类:

 

if ([object conformsToProtocol:@protocol(MyDelegateProtocol)])

{

  // Do something with the delegate

}有件事稍微对协议造成混淆就是事实上他们通过比较名字被用作相等性测试。如果两个协议同名的话,在运行时,你没法区分哪一个对象实现。论证就是允许直接从源代码文件中所展示的用@protocol()测试性能,而这些源文件不能存取协议描述但能导致混淆。

 

拓展类

Objective-C中有件事就是没有直接类比C++中类别的思想—被添加到现存的类中方法的集合。一旦类别被装载,他们就和其他方法没有区别。类别象类,和接口和实现一起声明。

 

类别接口可以被使用而不用回应公开给其他方法的实现,象给出的C++中的友元类。如果你在类中实现方法,但没有在接口中声明他们,你会得到一个编译时警告。你可以象这样定义类别接口:

@interface AnObject (Private)

- (void) privateMethod;

@end 如果你把它放在实现文件的顶部,当你发送一个-privateMathod消息给一个AnObject实例就不会得到运行时警告。名字是无括弧的就是类别的名字。这可以是任何。注意,没有@implementation部分,这就像C语言头部分的函数说明。如果在C中没有函数实现的回应,会得到链接错误。如果在Objective-C中没有相应方法实现回应,会得到一个运行时异常。

 

通过使用类别你可以提供额外的方法实现,以同种方式当你提供“正式”方法时:

 

@implementation AnObject (NewMethods)

- (void) newMethod

{

  ...

}

@end 如果你发送一个-newMethod消息给AnObject的任何一个实例,这个方法都会被调用。你可以使用这个来取代以你自己的版本对象中现存的方法,所以,对询问中的对象你不必存储到源代码。这种方式常常用于类库中各种方法,但也可以用于在没有源代码的情况下修补第三方类库的bug

 

缺少归档类别的一部分就是类库允许你对现存对象的协议增加一致性。如果你的类别采用了协议,还没有对每个方法提供实现,你会得到编译时警告,并且会进行运行时测试来检查一致性。在étoilé中我们使用这种便利对集合协议增加一致性,假设Foundation中所有集合类都有一个持久接口。

 

非正式协议

Objective-C一个相当普遍的模式就是非正式协议的思想,而这是方法的集合就是一个类有可能不被实现。通常用作托管对象。在Java中,非常普遍用托管来实现接口。这种方式有很多方法,而一些被实现作为没有方法体的方法,而这不是详尽的。

 

Objective-C中有两种方式来定义非正式协议。第一种方式通过定义基类上的类别来提供对每个方法的空实现。这意味着每个类将会回应接口中的消息,但只有这样才能显式地实现方法来做任何事。你可以输入一些源文件来使用非正式协议。

 

@implementation NSObject (MyInformalProtocol)

- (void) handleSomethingFrom:(id) sender {}

@end 然后你可以轻松的发送一个handleSomethingFrom: 消息给托管对象。注意,你不需要一个@interface部分来划分接口和类别实现。接口是私有的,除了你的类调用这个方法不在需要其它调用,所以不必公开接口。虽然这个技术简单,在很多方面还不是很理想的,因为涉及到用大多数未使用过的cruft来填充父对象委派表(Apple运行时取决于怎样实现隐藏这会导致轻微的性能衰减)

 

另一个选择就是执行运行时测试,如果你给对象发送一个respondsToSelector:消息,你可以找出是否实现了一个命名方法。对于托管,你可以为方法缓存IMP并且在将来直接调用。在你的setDelegate:方法,你将会这样做:

 

handleMethod = NULL;

if([delegate respondsToSelector:@selector(handleSomethingFrom:))

{

  handleMethod = [delegate methodForSelector: @selector(handleSomethingFrom:)];

}然后在你是用的地方,你会这么做:

 

// 等价于[delegate handleSomethingFrom:self];

if (NULL != handleMethod

{

  handleMethod(delegate, @selector(handleSomethingFrom:), self);

}Objective-C 2.0 提供了第三个选择,在协议声明中直接使用@optional。这是对空间的极大浪费,因为你需要执行运行时测试来决定一个对象是否遵守一个协议,但事实上从这个协议实现了一个可选择的方法。

 

二次指派

C++,你不需要发送消息,你可以调用成员方法。这是个重要区分,因为这意味着调用方法语义上近似于函数调用。Objective-C对抽象新增加一层。如果你给Objective-C对象发送消息,而这个对象不能解析,将会抛出一个异常。然而,这并不是通过语言做的。

 

运行时库当对于选择器没找到方法时有个回滚机制。它调用一个方法,这个方法对一些类型信息内省接收器,在一个NSInovocation包装调用,然后给对象传-forwardInvocation:方法。

 

NSInovocation对象包装了接收器,选择器,和参数。对于高位信息你可以使用这种思想。考虑下面的例子用例:

 

 [[anArray map] toUppercase];在数组上-map方法返回一个代理对象,这个对象带有forwardInvocation: 方法实现这样:

 

- (void) forwardInvocation:(NSInvocation*)anInvocation

{

 SEL selector = [anInvocation selector];

 NSMutableArray * mappedArray = [NSMutableArray array];

 FOREACHI(array, object)

 {

  if([object respondsToSelector:selector])

  {

   [anInvocation invokeWithTarget:object];

   id mapped;

   [anInvocation getReturnValue:&mapped];

   [mappedArray addObject:mapped];

  }

 }

 [anInvocation setReturnValue:mappedArray];

}FOREACHI宏来自étoilé,并且在NSEnumerator上执行一些IMP缓存。当你发送-toUppercase消息给映射代理,它会迭代数组中每个对象;检查是否回应了选择器,如果回应了,调用带参数的方法。返回值添加到新的数组中。

 

这在C++是不可能实现的。你可以使用命令模式做一些类似的工作,但只能在C++的头部实现你自己的委派机制。

 

总结

Objective-C是一个非常小的语言,并且在这个系列里我们涵盖了很多内容,包括一些高级特性。然而,正如Smalltalk,也稍微有些欺骗性。Objective-C语言的核心非常简单,一些Objective-C行为来自库。这通常是OpenStep规格的一个实现,类似GNUstep或者AppleCocoa,并且熟悉库的话可以带来长久的成就。

原创粉丝点击