iOS-Runtime之SELIMPMethod

Posted MinggeQingchun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS-Runtime之SELIMPMethod相关的知识,希望对你有一定的参考价值。

SEL、IMP、Method之间的关系:一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。具体的分析如下:

参考苹果官方公开源码

objc4源码在线浏览

objc4源码下载

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;//方法的实现
}    

MethodMethod是一个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_getInstanceMethodclass_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:方法来达到相同目的。

参考文章

SEL、Method和IMP区别

深入探究SEL,Method,IMP

以上是关于iOS-Runtime之SELIMPMethod的主要内容,如果未能解决你的问题,请参考以下文章

iOS-Runtime之unrecognized selector sent to instance/class 防护Crash

iOS-Runtime在开发中的使用及相关面试题

iOS-Runtime的那些事...编辑中....

ios-Runtime机制

IOS-Runtime(消息机制)

(转)iOS-Runtime知识点整理