从 C++到 Objective-C(5):类和对象(续二)
成员函数的指针:选择器
在 Objective-C 中,方法具有包含了括号和标签的特殊语法。普通的函数不能使用这种语法。在 Objective-C 和 C 语言中,函数指针具有相同的概念,但是对于成员函数指针则有所不同。
在 C++ 中,尽管语法很怪异,但确实兼容 C 语言的:成员函数指针也是基于类型的。
C++
class Foo
{
public:
int f(float x) {...}
};
Foo bar
int (Foo::*p_f)(float) = &Foo::f; // Foo::f 函数指针
(bar.*p_f)(1.2345); // 等价于 bar.f(1.2345);
在 Objective-C 中,引入了一个新的类型:指向成员函数的指针被称为选择器 selector。它的类型是 SEL,值通过 @selector 获得。@selector 接受方法名(包括 label)。使用类 NSInvocation 则可以通过选择器调用方法。大多时候,工具方法族 performSelector: (继承自 NSObject)更方便,约束也更大一些。其中最简单的三个是:
-(id) performSelector:(SEL)aSelector;
-(id) performSelector:(SEL)aSelector withObject:(id)anObjectAsParameter;
-(id) performSelector:(SEL)aSelector withObject:(id)anObjectAsParameter
withObject:(id)anotherObjectAsParameter;
这些方法的返回值同被调用的函数的返回值是一样的。对于那些参数不是对象的方法,应该使用该类型的包装类,如 NSNumber 等。NSInvocation 也有类似的功能,并且更为强大。
按照前面的说法,我们没有任何办法阻止在一个对象上面调用方法,即便该对象并没有实现这个方法。事实上,当消息被接收到之后,方法会被立即触发。但是,如果对象并不知道这个方法,一个可被捕获的异常将被抛除,应用程序并不会被终止。我们可以使用 respondsToSelector: 方法来检查对象是否可被触发方法。
最后,@selector 的值是在编译器决定的,因此它并不会减慢程序的运行效率。
Objective-C
@interface Slave : NSObject {}
-(void) readDocumentation:(Document*)document;
@end
// 假设 array[] 是包含 10 个 Slave 对象的数组,
// document 是一个 Document 指针
// 正常的方法调用是
for(i=0 ; i<10 ; ++i)
[array[i] readDocumentation:document];
// 下面使用 performSelector: 示例:
for(i=0 ; i<10 ; ++i)
[array[i] performSelector:@selector(readDocumentation:)
withObject:document];
// 选择器的类型是 SEL
// 下面代码并不比前面的高效,因为 @selector() 是在编译器计算的
SEL methodSelector = @selector(readDocumentation:);
for(i=0 ; i<10 ; ++i)
[slaves[i] performSelector:methodSelectorwithObject:document];
// 对于一个对象“foo”,它的类型是未知的(id)
// 这种测试并不是强制的,但是可以避免没有 readDocumentation: 方法时出现异常
if ([foo respondsToSelector:@selector(readDocumentation:)])
[foo performSelector:@selector(readDocumentation:) withObject:document];
因此,选择器可被用作函数参数。通用算法,例如排序,就可以使用这种技术实现。
严格说来,选择器并不是一个函数指针。它的底层实现是一个 C 字符串,在运行时被注册为方法的标识符。当类被加载之后,它的方法会被自动注册到一个表中,所以 @selector 可以很好的工作。根据这种实现,我们就可以使用 == 来判断内存地址是否相同,从而得出选择器是否相同,而无需使用字符串函数。
方法的真实地址,也就是看做 C 字符串的地址,其实可以看作是 IMP 类型(我们以后会有更详细的说明)。这种类型很少使用,除了在做优化的时候。例如虚调用实际使用选择器处理,而不是 IMP。等价于 C++ 函数指针的 Objective-C 的概念是选择器,也不是 IMP。
最后,你应该记得我们曾经说过 Objective-C 里面的 self 指针,类似于 C++ 的 this 指针,是作为每一个方法的隐藏参数传递的。其实这里还有第二个隐藏参数,就是 _cmd。_cmd 指的是当前方法。
@implementation Foo
-(void) f:(id)parameter // 等价于 C 函数 void f(id self, SEL _cmd,id parameter)
{
id currentObject = self;
SEL currentMethod = _cmd;
[currentObjectperformSelector:currentMethod
withObject:parameter]; // 递归调用
[self performSelector:_cmd withObject:parameter]; // 也是递归调用
}
@end
参数的默认值
Objective-C 不允许参数带有默认值。所以,如果某些参数是可选的,那么就应当创建多个方法的副本。在构造函数中,这一现象成为指定构造函数(designated initializer)。
可变参数
Objective-C 允许可变参数,语法同 C 语言一样,使用 … 作为最后一个参数。这实际很少用到,即是 Cocoa 里面很多方法都这么使用。
匿名参数
C++ 允许匿名参数,它可以将不使用的参数类型作为一种占位符。Objective-C 不允许匿名参数。
原型修饰符(const,static,virtual,”= 0″,friend,throw)
在 C++ 中,还有一些可以作为函数原型的修饰符,但在 Objective-C 中,这都是不允许的。以下是这个的列表:
· const:方法不能使用 const 修饰。既然没有了 const,也就不存在 mutable 了;
· static:用于区别实例方法和类方法的是原型前面的 – 和 +;
· virtual:Objective-C 中所有方法都是 virtual 的,因此没有必要使用这个修饰符。纯虚方法则是声明为一个典型的协议 protocol;
· friend:Objective-C 里面没有 friend 这个概念;
· throw:在 C++ 中,可以指定函数会抛除哪些异常,但是 Objective-C 不能这么做。