runtime官方
Objective-C Runtime Programming Guide - Dynamic Method Resolution
1、添加方法
- 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
- 经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法
方法:
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
+ (BOOL)resolveInstanceMethod:(SEL)se//动态对象方法
+ (BOOL)resolveClassMethod:(SEL)sel;//动态类方法
通过重写上面方法,调用class_addMethod();函数来动态添加方法,同时返回YES即可,如果返回NO,则会进入消息转发机制.
写法一:
//可以根据这个方法来取到
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(drive)];
}
void startEngine(id self, SEL _cmd, NSString *brand) {
NSLog(@"my %@ car starts the engine", brand);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, (IMP)startEngine, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
写法二:使用oc的方法
//使用这个来调用
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(drive)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
////获取类中的某个实例方法(减号方法)
Method exchangeM = class_getInstanceMethod([self class], @selector(startEngine:));
class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), method_getTypeEncoding(exchangeM));
//这样就拿到了eatWithPersonName方法的type encodings 用作class_addMethod的第四个参数
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)startEngine:(NSString *)brand {
NSLog(@"my %@ car starts the engine", brand);
}
其中 class_getMethodImplementation 意思就是获取 SEL 的具体实现的指针(拿到方法eatWithPersonName的IMP指针)
然后创建一个新的类「DynamicSelector」,在这个新类中我们实现对「Car」的动态添加方法
参数解释:
cls 参数表示需要添加新方法的类
name 参数表示 selector 的方法名称
imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法
最后一个参数 *types 表示我们要添加的方法的返回值和参数
char *types
比如:”v@:”意思就是这已是一个void类型的方法,没有参数传入。
再比如 “i@:”就是说这是一个int类型的方法,没有参数传入。
再再比如”i@:@”就是说这是一个int类型的方法,又一个参数传入
performSelector: withObject:是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。
所以这也是runtime的一种应用方式.所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣- (BOOL)respondsToSelector:(SEL)aSelector;来在运行时判断对象是否响应此方法
method相关的方法
//判断类中是否包含某个方法的实现
BOOL class_respondsToSelector(Class cls, SEL sel)
//获取类中的方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//为类添加新的方法,如果方法该方法已存在则返回NO
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//替换类中已有方法的实现,如果该方法不存在添加该方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//获取类中的某个实例方法(减号方法)
Method class_getInstanceMethod(Class cls, SEL name)
//获取类中的某个类方法(加号方法)
Method class_getClassMethod(Class cls, SEL name)
//获取类中的方法实现
IMP class_getMethodImplementation(Class cls, SEL name)
//获取类中的方法的实现,该方法的返回值类型为struct
IMP class_getMethodImplementation_stret(Class cls, SEL name)
//获取Method中的SEL
SEL method_getName(Method m)
//获取Method中的IMP
IMP method_getImplementation(Method m)
//获取方法的Type字符串(包含参数类型和返回值类型)
const char *method_getTypeEncoding(Method m)
//获取参数个数
unsigned int method_getNumberOfArguments(Method m)
//获取返回值类型字符串
char *method_copyReturnType(Method m)
//获取方法中第n个参数的Type
char *method_copyArgumentType(Method m, unsigned int index)
//获取Method的描述
struct objc_method_description *method_getDescription(Method m)
//设置Method的IMP
IMP method_setImplementation(Method m, IMP imp)
//替换Method
void method_exchangeImplementations(Method m1, Method m2)
//获取SEL的名称
const char *sel_getName(SEL sel)
//注册一个SEL
SEL sel_registerName(const char *str)
//判断两个SEL对象是否相同
BOOL sel_isEqual(SEL lhs, SEL rhs)
//通过块创建函数指针,block的形式为^ReturnType(id self,参数,...)
IMP imp_implementationWithBlock(id block)
//获取IMP中的block
id imp_getBlock(IMP anImp)
//移出IMP中的block
BOOL imp_removeBlock(IMP anImp)
//调用target对象的sel方法
id objc_msgSend(id target, SEL sel, 参数列表...)
实际使用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 动态添加eat方法
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
备注:有些文字来自网友的帖子,本帖用于记录学习过程