成员函数的指针:选择器
在 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:methodSelector withObject: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; [currentObject performSelector: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 不能这么做。
2 评论
关于NSInvocation 的用法能不能介绍点
这个可以到网上找找的哦,有很多具体介绍的文章,或者看看 Apple 的文档。