iOS-Runtime之SELIMPMethod
Posted MinggeQingchun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS-Runtime之SELIMPMethod相关的知识,希望对你有一定的参考价值。
SEL、IMP、Method之间的关系:一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。具体的分析如下:
参考苹果官方公开源码
1、SEL
在上述苹果官网公开源码objc4的objc.h文件中,定义如下:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL:方法选择器(翻译成中文叫做选择子或者选择器),表示一个selector的指针,代表方法的名称,仅以名字来识别。
SEL代表方法在Runtime期间的标识符。为SEL类型,虽然SEL是objc_selector
结构体指针,但实际上它只是一个C字符串。
在类加载的时候,编译器会生成与方法相对应的选择子,并注册到Objective-C的Runtime运行系统。
无论什么类里,无论类是否存在依存关系,只要方法名相同,SEL就相同。
项目里的所有SEL都保存在一个NSSet集合里(NSSet集合里的元素不能重复),所以查找对应方法,只要找到对应的SEL就可以了。
SEL实际是根据方法名hash化了的字符串
SEL sel_registerName(const char *str)//向runtime system注册一个方法名。如果方法名已经注册,则放回已经注册的SEL
SEL sel_getUid(const char *str)//同上
@selector(<#selector#>)//oc编译器提供的
SEL NSSelectorFromString(NSString *aSelectorName)//OC字符串转化
SEL method_getName ( Method m );//根据Method结构体获取
SEL的操作函数
// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
//判断方法名是否映射到某个函数实现上
BOOL sel_isMapped(SEL sel);
虽然SEL是方法的唯一标识,但不同的类调用名字相同的方法应该如何处理,接下来就要用到IMP
2、IMP
代码定义:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
可以简化为:
/// A pointer to the function of a method implementation.
typedef id (*IMP)(id, SEL , ...);
IMP:代表函数指针,指向方法实现的首地址,即函数执行的入口。
该函数使用标准的C调用。
第一个参数指向self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;
第二个参数代表方法的选择子;...代表可选参数,前面的id代表返回值。
那么,XX调用了XXX方法,其参数为XX都确定下来了。
IMP的高级作用
既然上述元素都确定下来了,那么就可以直接绕过Runtime的消息传递机制,直接执行IMP指向的函数了。省去了一些列的查找,直接向对象发送消息,效率会高一些。
IMP imp_implementationWithBlock(id block)//根据代码块获取IMP,其实就是代码块与IMP关联
IMP method_getImplementation(Method m) //根据Method获取IMP
[[objc Class] instanceMethodForSelector:SEL]//根据OC方式获取IMP
获取一个方法的IMP时候可以直接调用IMP
IMP imp = method_getImplementation(Method m);
id objc = imp(id,SEL,argument);//objc用来保存方法的返回值,id表示调用这个方法的对象,SEL是Method的选择器,argument是方法的参数。
3、Method
runtime.h文件中,代码定义如下:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
objc_method结构体定义如下:
/// Method
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;//方法名
char *method_types OBJC2_UNAVAILABLE;//参数返回值字符串描述
IMP method_imp OBJC2_UNAVAILABLE;//方法的实现
}
Method:Method是一个objc_method
结构体指针,该结构体中包含一个SEL和IMP。
实际上相当于在SEL和IMP之间作了一个映射。有了Method,SEL就可以找到对应的IMP,从而调用方法。对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。
method_name:方法名,类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
method_types:方法类型,是个char指针,其实存储着方法的参数类型和返回值类型,即是Type Encoding编码。
method_imp:指向方法的实现,本质上是一个函数的指针,就是前面讲到的Implementation。
Method操作函数如下:
方法操作主要有以下函数:
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation 一个Objective-C方法是一个简单的C函数,它至少包含两个参数–self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数
class_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。
class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。
class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。
参考文章
以上是关于iOS-Runtime之SELIMPMethod的主要内容,如果未能解决你的问题,请参考以下文章
iOS-Runtime之unrecognized selector sent to instance/class 防护Crash