Objective-C中一种消息处理方法performSelector: withObject: Objective-C中调用函数的方法是“消息传递”,这个和普通的函数调用的区别是,你可以随时对一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以Objective-C可以在runtime的时候传递人和消息。 首先介绍两个方法 SEL和@selector 根据AppleObjective-C Runtime Reference官方文档这个传递消息的函数就是 id objc_msgSend(id theReceiver, SEL theSelector, …) theReceiver是接受消息的对象类型是id,theSelector是消息名称类型是SEL。下边代码我们来看看如何来生成一个SEL,如果传递消息。 首先建立一个简单的函数 - (void) fooNoInputs { NSLog(@"Does nothing"); } 然后调用它 [self performSelector:@selector(fooNoInputs)]; 第二个试验看看如何在消息中传递参数 我们建立一个有input参数的函数 - (void) fooOneIput:(NSString*) first { NSLog(@"Logs %@", first); } 然后调用它 [self performSelector:@selector(fooOneInput:) withObject:@"first"]; 第三个试验更多的参数 - (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second { NSLog(@"Logs %@ then %@", first, second); } 然后调用它 [self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second"]; 第四个试验如何建立动态的函数,然后调用他们?我们需要建立一个selector SEL myTestSelector = @selector(myTest:); 并且我们调用的函数在另外一个Class内 - (void)abcWithAAA: (NSNumber *)number { int primaryKey = [number intValue]; NSLog("%i", primaryKey); } MethodForSelectors * mfs = [[MethodForSelectors alloc]init]; NSArray *Arrays = [NSArray arrayWithObjects:@"AAA", @"BBB", nil]; for ( NSString *array in Arrays ){ SEL customSelector = NSSelectorFromString([NSStringstringWithFormat:@"abcWith%@:", array]); mfs = [[MethodForSelectors alloc] performSelector:customSelector withObject:0]; } 注意:updated at 20120606 1.如果使用了ARC会产生“performSelector may cause a leak because its selector is unknown”警告 2.这种方式当传入一个不符合约定的消息时会继续运行并不报错。例如应该传入2个参数,但只传入1个参数。或传入了3个参数,第三个参数不会被初始化。 还有一种调用其他Class Function的方法是,但是不能有参数,我们这里假设没有参数,那么就可以这样 [mfs customSelector]; 完整的代码: @implementation ClassForSelectors - (void) fooNoInputs { NSLog(@"Does nothing"); } - (void) fooOneIput:(NSString*) first { NSLog(@"Logs %@", first); } - (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second { NSLog(@"Logs %@ then %@", first, second); } - (NSArray *)abcWithAAA: (NSNumber *)number { int primaryKey = [number intValue]; NSLog("%i", primaryKey); } - (void) performMethodsViaSelectors { [self performSelector:@selector(fooNoInputs)]; [self performSelector:@selector(fooOneInput:) withObject:@"first"]; [self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second"]; } - (void) performDynamicMethodsViaSelectors { MethodForSelectors * mfs = [MethodForSelectors alloc]; NSArray *Arrays = [NSArray arrayWithObjects:@"AAA", @"BBB", nil]; for ( NSString *array in Arrays ){ SEL customSelector = NSSelectorFromString([NSStringstringWithFormat:@"abcWith%@:", array]); mfs = [[MethodForSelectors alloc] performSelector:customSelector withObject:0]; } } @end @implementation MethodForSelectors - (void)abcWithAAA: (NSNumber *)number { NSLog("%i", number); } @end
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 )。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
那OC是怎么实现动态调用的呢?下面我们来看看OC通过发送消息来达到动态调用的秘密。假如在OC中写了这样的一个代码:
1
|
[obj makeText];
|
其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成
1
|
objc_msgSend(obj,@selector(makeText));
|
首先我们来看看obj这个对象,iOS中的obj都继承于NSObject。
1
2
3
|
@interface NSObject <nsobject> {
Class isa OBJC_ISA_AVAILABILITY;
}</nsobject>
|
在NSObjcet中存在一个Class的isa指针。然后我们看看Class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
typedef struct objc_class *Class;
struct objc_class {
Class isa;
// 指向metaclass
Class super_class ;
// 指向其父类
const char *name ;
// 类名
long version ;
// 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info;
// 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ;
// 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars;
// 用于存储每个成员变量的地址
struct objc_method_list **methodLists ;
// 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache;
// 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols;
// 存储该类遵守的协议
}
|
我们可以看到,对于一个Class类中,存在很多东西,下面我来一一解释一下:
Class isa:指向metaclass,也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对 象方法(“-”开头的方法),普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方 法)。
Class super_class:指向父类,如果这个类是根类,则为NULL。
下面一张图片很好的描述了类和对象的继承关系:
注意:所有metaclass中isa指针都指向跟metaclass。而跟metaclass则指向自身。Root metaclass是通过继承Root class产生的。与root class结构体成员一致,也就是前面提到的结构。不同的是Root metaclass的isa指针指向自身。
Class类中其他的成员这里就先不做过多解释了,下面我们来看看:
@selector (makeText):这是一个SEL方法选择器。SEL其主要作用是快速的通过方法名字(makeText)查找到对应方法的函数指针,然后调用其函 数。SEL其本身是一个Int类型的一个地址,地址中存放着方法的名字。对于一个类中。每一个方法对应着一个SEL。所以iOS类中不能存在2个名称相同 的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。
下面我们就来看看具体消息发送之后是怎么来动态查找对应的方法的。
首先,编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector (makeText));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
NSClassFromString,NSSelectorFromString,isKingOfClass 1. NSClassFromString 这个方法判断类是否存在,如果存在就动态加载的,不存为就返回一个空对象; id myObj = [[NSClassFromString(@"MySpecialClass") alloc] init]; 正常情况下等价于:id myObj = [[MySpecialClass alloc] init]; 优点: 1, 弱化连接,因此并不会把没有的Framework也link到程序中。 2,不需要使用import,因为类是动态加载的,只要存在就可以加载。因此如果你的toolchain中没有某个类的头文件定义,而你确信这个类是可以用的,那么也可以用这种方法。 2. NSSelectorFromString 这个方法是上个方法的补充,也是动态加载实例方法。 SEL sel = NSSelectorFromString(@"doSomethingMethod:")//注意这个冒号,说明方法带有参数 if([object respondsToSelector:sel]) { [object performSelector:sel withObject:color]; //注意如果有两个参数,使用两个withObject:参数; }