首页 Objective-C 从 C++ 到 Objective-C(5):类和对象(续二)

从 C++ 到 Objective-C(5):类和对象(续二)

2 2

成员函数的指针:选择器

在 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 不允许匿名参数。

原型修饰符(conststaticvirtual= 0friendthrow

在 C++ 中,还有一些可以作为函数原型的修饰符,但在 Objective-C 中,这都是不允许的。以下是这个的列表:

  • const:方法不能使用const修饰。既然没有了const,也就不存在mutable了;
  • static:用于区别实例方法和类方法的是原型前面的 - 和 +;
  • virtual:Objective-C 中所有方法都是virtual的,因此没有必要使用这个修饰符。纯虚方法则是声明为一个典型的协议protocol
  • friend:Objective-C 里面没有friend这个概念;
  • throw:在 C++ 中,可以指定函数会抛除哪些异常,但是 Objective-C 不能这么做。

2 评论

willonboy's blog 2011年4月1日 - 13:36

关于NSInvocation 的用法能不能介绍点

回复
DevBean 2011年4月1日 - 13:45

这个可以到网上找找的哦,有很多具体介绍的文章,或者看看 Apple 的文档。

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2