现在的位置: 首页 > 综合 > 正文

(3)iPhone开发基础 - 选择器

2014年10月26日 ⁄ 综合 ⁄ 共 3500字 ⁄ 字号 评论关闭

通过上一章的学习,你可能很迷糊,这跟C/C++相差也太远了吧,是的,语法的表现形式上有很大的区别,但编程原理是相通的,只是表现形式上有区别而已。这一章

我们再引入一个比较新的概念:选择器,与其它语言相较,它显得很特别,但很容易理解,下面我们先看一张表:

类Test1
方法名 方法ID 方法地址
testFunc 1001 0x2001
testFunc: 1002 0x2002

类Test2
方法名 方法ID 方法地址
testFunc 1001 0x2003
testFunc: 1002 0x2004

程序在启动的时候就会为每个类创建一张与上面类似的表,来记录类的所有的方法名称、方法ID和方法地址,每个方法名对应的方法ID都是唯一的,即相同方法具有相同的方法ID。选择器主要就是利用方法ID的唯一性来做文章, 为了弄清楚它,我们先来看与上面表相同的两个类:

1) Test1

Test1.h

#import <Foundation/Foundation.h>

@interface Test1 : NSObject {    
}

-(void) testFunc;
-(id) testFunc: (NSString*) name;

@end

Test1.m

#import "Test1.h"

@implementation Test1

- (void) testFunc
{
    NSLog(@"Test1:no parameter");
}

- (id) testFunc: (NSString *) name
{
        return nil;
}
@end

2) Test2

Test2.h

#import <Foundation/Foundation.h>

@interface Test2 : NSObject {    
}

-(void) testFunc;
-(id) testFunc: (NSString*) name;

@end

Test2.m

#import "Test2.h"

@implementation Test2

- (void) testFunc
{
    NSLog(@"Test2:no parameter");
}

- (id) testFunc: (NSString *) name
{
   NSLog(@"Test2: with parameter");
   return nil;
}
@end

接下来,我们在main函数中实现选择器的调用:

3) main.m

#import <Foundation/Foundation.h>
#import "Test1.h"
#import "Test2.h"
int main (int argc, const char* argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasedPool alloc] init];
    SEL testFunc;
    testFunc = @selector(testFunc);
    SEL testFunc2;
    testFunc2 = NSSelectorFromString(@"testFunc:");
    NSLog(@"The testFunc name is: %@", NSStringFromSelector(testFunc));
    NSLog(@"The testFunc: name is: %@", NSStringFromSelector(testFunc:));
    Test1* t1 = [[Test1 alloc] init];
    Test2* t2 = [[Test2 alloc] init];
    [t1 performSelector:testFunc withObject: @"with parameter"];
    objc_msgSend(t1, testFunc);
    objc_msgSend(t2, testFunc:, @"with parameter");
    [t1 release];
    [t2 release];
    [pool drain];
    return 0;
}

由上可看出,与selector相关的函数有3个:

1) SEL 变量名 = @selector(选择器名称), 通过名称获取选择器

2) SEL 变量名 = NSSelectorFromString(@"选择器名称"); 通过方法名称字符串获取选择器

3) NSString* 变量名 = NSStringFromSelector(选择器); 获取选择器的方法名

下面我们介绍两个特殊的变量类型:

1) id

id类型与C/C++的void* 类似,它表示对象指针,但它与void*有很大的区别, objc中对象之间的调用(暂且称之为调用)是通过发送消息的方式实现的,就是在调用某个对象的方法时,是给该对象发送消息,这里选择器起很大的作用,对象在得到消息后,寻找选择器方法表,得到函数地址,然后进行调用,这样,这里的id不需要任何类型转换,即可对对象进行消息的发送(即方法的调用),XCode对其也进行了智能提示,你将任何对象传递给id声明的变量,该变量就可智能提示所对应的类的所有方法,是不是很优美的面向对象的实现方式?

你试想,我们用id作为参数,动态创建类(可通过反射实现,后续章节中会介绍),然后通过配置文件动态执行方法,是不是可以很智能的实现对象化呢?

2) SEL

SEL类型就是为方法ID而产生的,所以它是用来表示方法ID的变量类型。

好,到此,我们可以理解选择器的两个特性: a. 可以通过名字找到方法,即可以通过名字调用方法,这是C/C++、C#和Java所没有的特性。b. 可以动态的改变方法的名字。

最后,还有个objc_msgSend, 下面引用自苹果官方文档对于消息机制的说明:

objc_msgSend函数

Objective-C中,消息是直到运行的时候才和方法实现绑定的。编译器会把一个消息表达式,
[receiver message]
转换成一个对消息函数objc_msgSend的调用。该函数有两个主要参数:消息接收者和消息对应的方法名字——也就是方法选标:
objc_msgSend(receiver, selector)
同时接收消息中的任意数目的参数:
objc_msgSend(receiver, selector, arg1, arg2, ...)
该消息函数做了动态绑定所需要的一切:
它首先找到选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。
然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传给找到的方法实现。
最后,将方法实现的返回值作为该函数的返回值返回。
注意:编译器将自动插入调用该消息函数的代码。您无须在代码中显示调用该消息函数。
消息机制的关键在于编译器为类和对象生成的结构。每个类的结构中至少包括两个基本元素:
指向父类的指针。
类的方法表。方法表将方法选标和该类的方法实现的地址关联起来。例如,setOrigin::的方法选标和setOrigin::的方法实现的地址关联,display 的方法选标和display的方法实现的地址关联,等等。
当新的对象被创建时,其内存同时被分配,实例变量也同时被初始化。对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。通过该指针,对象可以访问它对应的类以及相应的父类。
注意:尽管严格来说这并不是Obective-C语言的一部分,但是在Objective-C运行时系统中对象需要有isa指针。对象和结构体struct objc_object(在objc/objc.h中定义)必须“一致”。然而,您很少需要创建您自己的根对象,因为从NSObject或者NSProxy继承的对象都自动包括isa变量。
当对象收到消息时,消息函数首先根据该对象的isa指针找到该对象所对应的类的方法表,并从表中寻找该消息对应的方法选标。如果找不到,objc_msgSend将继续从父类中寻找,直到NSObject类。一旦找到了方法选标, objc_msgSend则以消息接收者对象为参数调用,调用该选标对应的方法实现。
这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。
为了加快消息的处理过程,运行时系统通常会将使用过的方法选标和方法实现的地址放入缓存中。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。消息函数会首先检查消息接收者对象对应的类的缓存(理论上,如果一个方法被使用过一次,那么它很可能被再次使用)。如果在缓存中已经有了需要的方法选标,则消息仅仅比函数调用慢一点点。如果程序运行了足够长的时间,几乎每个消息都能在缓存中找到方法实现。程序运行时,缓存也将随着新的消息的增加而增加。




抱歉!评论已关闭.