转载自:Objective-C中的SEL、IMP和Class类型
1、SEL类型
例子:
SEL say;
SEL skin;
Objective-C 在编译的时候, 会根据方法的名字(包括参数序列),生成一个用 来区分这个方法的唯一的一个 ID,这个 ID 就是 SEL 类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么它们的 ID 都是相同的。就是 说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同 那么 ID 就是一样的。
在 程序执行的时候,我们可以方便的通过方法的名字,获取到方法的 ID 也就是我们所说的 SEL, 反之亦然。具体的使用方法如下:
[pre]1 SEL 变量名 = @selector(方法名字);2 SEL 变量名 = NSSelectorFromString(方法名字的字符串);3 NSString *变量名 = NSStringFromSelector(SEL 参数);[/pre]
其中第 1 行是直接在程序里面写上方法的名字,第 2 行是写上方法名字的字符串,第 3 行是通 过 SEL 变量获得方法的名字。我们得到了 SEL 变量之后,可以通过下面的调用来给一个对象发 送消息:
[对象 performSelector:SEL 变量 withObject:参数 1 withObject:参数 2];
这样的机制大大的增加了我们的程序的灵活性,我们可以通过给一个方法传递 SEL 参数,让这 个方法动态的执行某一个方法;我们也可以通过配置文件指定需要执行的方法,程序读取配置文 件之后把方法的字符串翻译成为 SEL 变量然后给相应的对象发送这个消息。
从效率的角度上来说,执行的时候不是通过方法名字而是方法 ID 也就是一个整数来查找方法, 由于整数的查找和匹配比字符串要快得多,所以这样可以在某种程度上提高执行的效率。
2、IMP类型
[pre] 例子:void(*setSkinColor_Func) (id, SEL, NSString*);//定义一个函数指针(传统C语言的处理方式) IMP say_Func;//定义一个IMP方式的函数指针(obj-C中推荐的方式) 说白了IMP就是实现方法,给一个方法起个名字,实现动态调用,不用再使用[对象 message]的方式。 IMP 的定义如下: typedef id (*IMP)(id, SEL, ... );[/pre]
这个格式正好和我们在第一行代码里面的函数指针的定义是一样的。
我们取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我 们就可以像普通的 C 语言函数调用一样使用这个函数指针。当然我们可以把函数指针作为参数 传递到其他的方法,或者实例变量里面,从而获得极大的动态性。我们获得了动态性,但是付出 的代价就是编译器不知道我们要执行哪一个方法所以在编译的时候不会替我们找出错误,我们只 有执行的时候才知道,我们写的函数指针是否是正确的。所以,在使用函数指针的时候要非常准 确地把握能够出现的所有可能,并且做出预防。尤其是当你在写一个供他人调用的接口 API 的 时候,这一点非常重要。
3、Class
类在 Objective-C 也为我们准备了类似的机制, Class 类型。当一个类被正确的编译过后,在这个编译成功的类里面,存在一个变量用于保存这 个类的信息。我们可以通过一个普通的字符串取得 这个 Class,也可以通过我们生成的对象取 得这个 Class。Class 被成功取得之后,我们可以把这个 Class 当作一个已经定义好的类来使用 它。这样的机制允许我们在程序执行的过程当中,可以 Class 来得到对象的类,也可以在程序 执行的阶段动态的生成一个在编译阶段无法确定的一个对象。
因为 Class 里面保存了一个类的所有信息,当然,我们也可以取得一个类的超类。关于 Class 类型,具体的使用格式如下:
[pre]1 Class 变量名 = [类或者对象 class];2 Class 变量名 = [类或者对象 superclass];3 Class 变量名 = NSClassFromString(方法名字的字符串);4 NSString *变量名 = NSStringFromClass(Class 参数);[/pre]
第一行代码,是通过向一个类或者对象发送 class 消息来获得这个类或者对象的 Class 变量。 第二行代码,是通过向一个类或者对象发送 superclass 消息来获得这个类或者对象的超类的
Class 变量。
第三行代码,是通过调用 NSClassFromString 函数,并且把一个字符串作为参数来取得 Class 变量。这个在我们使用配置文件决定执行的时候的类的时候,NSClassFromString 给我们带来 了极大的方便。
第四行代码,是 NSClassFromString 的反向函数 NSStringFromClass,通过一个 Class 类 型作为变量取得一个类的名字。
当我们在程序里面通过使用上面的第一,二或者第三行代码成功的取得一个 Class 类型的变量, 比如说我们把这个变量名字命名为 myClass,那么我们在以后的代码种可以把 myClass 当作 一个我们已经定义好的类来使用,当然我们可以把这个变量作为参数传递到其他的方法当中让其 他的方法动态的生成我们需要的对象。
-----------------------------------------------------------------------
一.什么是IMP
IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它;但有些时候我们希望获取到IMP进行直接调用。
二.Objetive-C中的Method结构
在Objecitve-C中,在类中对每一个方法有一个在运行时构建的数据结构,在Objective-C 2.0中,此结构对用户不可见,但仍在内部存在。
[pre]struct objc_method{ SEL method_name; char * method_types; IMP method_imp;};typedef objc_method Method;[/pre]每个方法有3个属性
方法名:方法名为此方法的签名,有着相同函数名和参数名的方法有着相同的方法名。方法类型:方法类型描述了参数的类型。IMP: IMP即函数指针,为方法具体实现代码块的地址,可像普通C函数调用一样使用IMP。
由于Method的内部结构不可见,所以不能通过method->method_name的方式访问其内部属性,只能Objective-C运行时提供的函数获取。
[pre]SEL method_getName(Method method);IMP method_getImplementation(Method method);const char * ivar_getTypeEncoding(Ivar ivar);[/pre]还有一些其他函数来获取方法的各种属性,具体可见Objective-C Runtime Reference。
四.获取当前方法的默认IMP(default IMP)
NSObject对象提供了两个方法来获取的IMP
[pre]- (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector;[/pre]使用methodForSelector方法时,若向类(class)发送消息,则aSelector应该是类方法(class method);若向实例对象(instance)发送消息,则aSelector应该为实例对象方法(instance method)。使用instanceMethodForSelector可向类请求实例方法的IMP。
那么获取当前方法的IMP,可使用self对象和隐含的_cmd参数。
[pre]IMP current = [self class] instanceMethodForSelector:_cmd];[/pre]methodForSelector返回的IMP是default IMP,即发送消息时会调用的IMP。但可能有其他情况不是这样的,但到底什么情况下呢?不懂,如下:
methodForSelector: only returns the default IMP that will be invoked by a send message but you could have arrived at the current method through a super invocation, or a direct invocation of the IMP itself. The approach shown in this post gets the correct IMP no matter how it was invoked.
四. 另一种hack的方式获取当前方法IMP
GCC有个内建(build-in)函数,可获取当前栈帧的返回地址(参数0表示当前栈帧)。
[pre]__builtin_return_address(0)[/pre]这里有两个假设
方法的实现是连续的。在方法中调用子函数,在子函数中调用__builtin_return_address(0)得到的返回地址在当前方法实现的内部。
那么可以得出结论,当前方法实现的起始地址(即IMP)肯定在的子函数返回地址的前面,而且会比任何其他方法离的更近。则可通过如下方法寻找IMP。
当当前方法中,调用一个寻找IMP的子函数,将当前类和_cmd参数传递给子函数;在子函数中使用__builtin_return_address(0)获得返回地址,遍历当前类和当前父类的所有方法,寻找method_name与_cmd相等,而method_imp在__builtin_return_address(0)之前,且离其最近,则此IMP即为当前的的IMP。
[pre]#import IMP impOfCallingMethod(id lookupObject, SEL selector){ NSUInteger returnAddress = (NSUInteger)__builtin_return_address(0); NSUInteger closest = 0; // Iterate over the class and all superclasses Class currentClass = object_getClass(lookupObject); while (currentClass) { // Iterate over all instance methods for this class unsigned int methodCount; Method *methodList = class_copyMethodList(currentClass, &methodCount); unsigned int i; for (i = 0; i < methodCount; i++) { // Ignore methods with different selectors if (method_getName(methodList) != selector) { continue; } // If this address is closer, use it instead NSUInteger address = (NSUInteger)method_getImplementation(methodList); if (address < returnAddress && address > closest) { closest = address; } } free(methodList); currentClass = class_getSuperclass(currentClass); } return (IMP)closest;}[/pre]其使用方法为:
[pre]- (void)myMethodWithParam1:(int)someParameter andParam2:(int)otherParameter{ IMP myImplementation = impOfCallingInstanceMethod([self class], _cmd); ...}[/pre]另外还有更快速的方法:获取当前方法的返回地址,然后通过此地址回溯到父函数的IMP内部,在父函数IMP中寻找调用当前函数的位置,即可知道当前函数的IMP,不过此方法是平台相关的。
注:此方法假设IMP地址与子函数返回地址之间是连续的,中间不会有其他方法IMP。如果将impOfCallingMethod放到block代码中,则此条件不再满足,不再适用。(待验证)
参考:
--------------------------------------------------------------------------------------------------
转载自:Objective-C 2.0 with Cocoa Foundation--- 5,Class类型,选择器Selector以及函数指针
这篇文章非常好,直接移步链接吧~~~
转载自:IOS SEL (@selector) 原理及使用总结(一)
IOS SEL (@selector) 原理及使用总结(二)
这篇文章也是超赞,讲到多参数时的调用方法。
转载自:深入浅出 Cocoa 之消息
------------------------------------
更多文章可寻 runtime机制。
网友评论