参考内容
Objective-C Runtime 运行时之三:方法与消息
方法中 SEL, IMP, Method 的定义与关系
![示意图](http://upload-images.jianshu.io/upload_images/1180547-f7906aa5b8893729.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
SEL
- 代表方法的名称。仅以名字来识别。
- 不论两个类是否存在依存关系,只要他们拥有相同的方法名,那么他们的
SEL
都是相同的。比如,有n个viewcontroller
页面,每个页面都有一个viewdidload
,每个页面的载入,肯定都是不尽相同的。但是我们可以通过打印,观察发现,这些viewdidload
的SEL
都是同一个:
SEL sel = @selector(methodName);
NSLog(@"address = %p",sel);// log输出为 address = 0x100002d72
- 因此类方法定义时,尽量不要用相同的名字,就算是变量类型不同也不行。否则会引起重复,例如:
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
IMP
- 定义:
id (*IMP)(id, SEL, ...)
个人理解:
-
SEL
只提供一个名字,但是至于这个名字属于谁,并不知道。A类和B类的SEL
实际实现IMP
是不一样的。IMP
其实是implementation
的缩写。 -
IMP
定义中存在两个参数,id
即为指向self的指针。SEL
就是上面说的方法名 - 综合来看,
IMP
可以理解为,我知道有一个方法,是SEL
告诉我的,但是这是哪个类的里面具体的实现方法,需要id
告诉我。两者确定后,我就可以获得某个指定的方法的内容了。IMP
相当于是函数指针。
Method
- 我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。具体操作流程我们将在下面讨论。
- 个人理解,Method相当于是把二者综合在一起,是一个完整的,我们传统意义上理解的方法
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:
方法来达到相同目的。
示例,实现 method swizzle
用runtime + category 实现
runtime
中 types
的说明
const char *types
一个定义该函数返回值类型和参数类型的字符串
举例说明
- 函数原型
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- 说明
class_addMethod([TestClass class], @selector(ocMethod:), (IMP)testFunc, "i@:@");
* `i`表示方法的返回类型,下面是`dash`中的截图
* 第一个
@
是必须有的,表示self. 也就是说,上面说的返回类型+@
是必备的。*
:@
表示传入的参数。如果有两个参数,就是:@@
.可参见下面的代码
BOOL class_addMethod(Class cls,SEL name,IMP imp, const char *types)
/**
* 一个参数
*
*/
int cfunction(id self, SEL _cmd, NSString *str) {
NSLog(@"%@", str);
return10;//随便返回个值
}
-(void) oneParam {
TestClass *instance = [[TestClassalloc]init];
// 方法添加
class_addMethod([TestClassclass],@selector(ocMethod:), (IMP)cfunction,"i@:@");
if ([instance respondsToSelector:@selector(ocMethod:)]) {
NSLog(@"Yes, instance respondsToSelector:@selector(ocMethod:)");
} else
NSLog(@"Sorry");
int a = (int)[instance ocMethod:@"我是一个OC的method,C函数实现"];
NSLog(@"a:%d", a);
}
/**
* 两个参数
*
*/
int cfunctionA(id self, SEL _cmd, NSString *str, NSString *str1) {
NSLog(@"%@-%@", str, str1);
return20;//随便返回个值
}
-(void) twoParam {
TestClass *instance = [[TestClass alloc]init];
class_addMethod([TestClass class],@selector(ocMethodA::), (IMP)cfunctionA,"i@:@@");
if ([instance respondsToSelector:@selector(ocMethodA::)]) {
NSLog(@"Yes, instance respondsToSelector:@selector(ocMethodA::)");
} else
NSLog(@"Sorry");
int a = (int)[instance ocMethodA:@"我是一个OC的method,C函数实现" :@"-----我是第二个参数"];
NSLog(@"a:%d", a);
}