ios-runtime添加方法

2018-10-23  本文已影响0人  命运建筑师fly

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<UITouch *> *)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<UITouch *> *)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

备注:有些文字来自网友的帖子,本帖用于记录学习过程

上一篇下一篇

猜你喜欢

热点阅读