SEL
SEL又叫选择器,是表示一个方法的selector的指针,其定义如下:
typedef struct objc_selector *SEL;
objc_selector结构体的详细定义没有在头文件中找到。方法的selector用于表示运行时方 法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。如下 代码所示:
person.m
SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);
dog.m
SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);
上面的输出都为:
2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72
两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL。所以在 Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致 Objective-C在处理相同方法名且参数个数相同但类型不同的方法方面的能力很差。如在某个类中定义以下两个方法:
- (void)setWidth:(int)width;
- (void)setWidth:(double)width;//此方法编译时报错
当然,不同的类可以拥有相同的selector,这个没有问题。实际根据SEL来调用方法的过程是通过SEL在类里找到对应的IMP然后由IMP去调用方。
工程中的所有的SEL组成一个Set集合,Set的特点就是唯一,因此SEL是唯一的。因此,如果我们想到这个方法集合中查找某个方法时,只需要去 找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了,可以说速度 上无语伦比!!但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。
IMP
IMP实际上是一个函数指针,指向方法实现的首地址。其定义如下:
id (*IMP)(id, SEL, ...)
这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针 了。
通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
Method
介绍完SEL和IMP,我们就可以来讲讲Method了。Method用于表示类定义中的方法,则定义如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。
iOS中用三种方式调用方法:
如下的printStr1
方法
1.直接调用
[self printStr:@"hello world 1"];
2.使用performSelector:withObject:
[self performSelector:@selector(printStr:) withObject:@"hello world 2"];
3.使用NSMethodSignature&NSInvocation
NSString *str = @"Test";
str = [str stringByAppendingString:@" AppendingString"];
NSLog(@"str: %@", str);
//需要调用的方法
SEL selector = @selector(stringByAppendingString:);
//获取方法签名
NSMethodSignature *signature = [str methodSignatureForSelector:selector];
//创建 NSInvocation 对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//设置消息接受对象
invocation.target = str;
//设置发送的消息
invocation.selector = selector;
//建议通过signature.numberOfArgument获取参数个数
//可以保证不多参,不少参
//这边减2的原因是:
//0位置的参数是 目标(self)
//1位置的参数是 selector(_cmd)
//所以2位置才是所需要的第一个参数
NSInteger paramCount = signature.numberOfArguments - 2;
NSLog(@"paramCount: %@", @(paramCount));
for (int i = 0; i < paramCount; i++) {
//设置参数
[invocation setArgument:&str atIndex:i + 2];
}
//执行 invocation
[invocation invoke];
//获取返回值
id returnValue = nil;
if (signature.methodReturnLength) {
[invocation getReturnValue:&returnValue];
}
NSLog(@"returnValue: %@", returnValue);
NSMethodSignature
用于描述method的类型信息:返回值类型,及每个参数的类型。
//获取类方法的签名
+ (NSMethodSignature*)instanceMethodSignatureForSelector:(SEL)aSelector
//获取实例方法的签名
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
介绍第三种获取NSMethodSignature的方法
+ (NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
如下:
-(void) test:(NSString*)str{
}
在OC中,每一种数据类型可以通过一个字符编码来表示(Objective-C type encodings)。例如字符‘@’代表一个object, 'i'代表int。 那么,由这些字符组成的字符数组就可以表示方法类型了。举个例子:上面提到的test:
对应的ObjCTypes 为 v@:@。
’v‘ : void类型,第一个字符代表返回值类型
’@‘ : 一个id类型的对象,第一个参数类型
’:‘ : 对应SEL,第二个参数类型
’@‘ : 一个id类型的对象,第三个参数类型,也就是-(void) test:(NSString*)str中的str。
NSInvocation
NSInvocation作为对象呈现的Objective-C消息。(An Objective-C message rendered as an object.)
NSInvocation
对象主要用于存储和转发对象之间和应用程序之间的消息,主要是NSTimer
对象和分布式对象系统。NSInvocation
对象包含Objective-C
消息的所有元素:目标(target),选择器(selector),参数和返回值。可以直接设置这些元素,并在NSInvocation
对象被dispatched时自动设置返回值。
使用invocationWithMethodSignature:方法创建NSInvocation
对象,不要使用alloc
和init
来创建