iOS Runtime(一)

来源:互联网 发布:国画淘宝梅花挂客厅 编辑:程序博客网 时间:2024/05/20 08:22

什么是Runtime

根据字面意思,可以解释为程序运行时,是oc的底层实现,那么Runtime具体是什么样呢?

  • 首先,看一下下面的代码
Person *p=[Person alloc]init];

这是我们经常使用的实例化对象的方法,那么,底层是怎么实现的呢?可以进行这样的拆分

Person * p = [Person alloc];p = [p init];//[p eat];[p performSelector:@selector(eat)];

首先如果没有实现eat方法[p eat]是会报错的,但是[p performSelector:@selector(eat)]不会,编译器认为运行时会动态添加该方法,所以没有实现也不会报错,这位动态添加方法打下了基础。然后,实例化的底层是怎么实现的,这就用到了Runtime。

Person * p = objc_msgSend(objc_getClass("Person"), @selector(alloc));p = objc_msgSend(p, @selector(init));objc_msgSend(p, @selector(eat));

那么,用clang转换为c语言后这句又是怎么实现的呢,其实跟这个几乎一样,就是利用Runtime

Person * p = ((HKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((HKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HKPerson"), sel_registerName("alloc")), sel_registerName("init"));

这样,将oc语言转换为c函数,这就是Runtime。

Runtime有什么用

  • 方法交换
    oc语言抛异常能力比较差,往往在代码量大的时候,检查错误比较头疼。例如
//现在我需要给URL做检测!!NSURL * url = [NSURL URLWithString:@"www.baidu.com/中文"];//NSURLRequest * request = [NSURLRequestrequestWithURL:url];//发送了NSLog(@"%@",request);

这样是可以正常发送请求的,即使url为null也可以发送请求。在代码量大的时候,这样的错误是很难检查出来的。那么,怎么办呢?可以新建一个NSURL的分类,添加一个URLWithString:方法,但是,难道每次使用这个方法都要导入一次头文件,这样非常繁琐。有没有什么简单的方法呢,可以不可以修改系统的方法呢?当然可以,这就要用到Runtime了。
新建一个分类写一个自己的方法, 方法中加上对url的判断:

+(instancetype)WRQ_URLWithString:(NSString *)URLStirng{    NSURL * url = [NSURL URLWithString:URLStirng];//调用了系统的方法了    if (url == nil) {        NSLog(@"哥么这个url是空");    }    return url;}

然后,就要利用Runtime交换方法了。在此之前,先明确一个问题,大家知道在app启动后,什么地方最早执行么?大多数人可能会想到main()函数,对,这是程序的主入口,但是,在加载函数之前,会先加载文件,即调用+load()方法,为了让修改后的方法对任何地方都适用,就要将交换方法写在load中。

+(void)load{    //class_getInstanceMethod:获取对象方法    //class_getClassMethod:获取类方法    Method URLWithStr = class_getClassMethod([NSURL class], @selector(URLWithString:));    Method HKURLWithStr = class_getClassMethod([NSURL class], @selector(HK_URLWithString:));    //交换    method_exchangeImplementations(URLWithStr, HKURLWithStr);}

这样,就完成了两个方法的交换,但是,运行程序,会进入死循环,这是为什么呢?因为交换方法后,URLWithString:实际上执行的是WRQ_URLWithString:这样就自己调用了自己,无限递归。应该改为

+(instancetype)WRQ_URLWithString:(NSString *)URLStirng{    NSURL * url = [NSURL WRQ_URLWithString:URLStirng];//调用了系统的方法了    if (url == nil) {        NSLog(@"哥么这个url是空");    }    return url;}

这样,就可以达到我们想要的效果,只要调用系统方法,就会检查url是否为空,成功地修改了系统方法。

  • 动态添加方法
    说到动态添加方法,就又要说到消息转发机制。这里主要讲动态添加方法,就简单地说说。在调用了[p performSelector:@selector(eat)]方法或者objc_msgSend(p, @selector(eat))后,系统会在类中寻找该方法,如果没有找到就会调用+(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel
    方法,如果return NO;就会继续调用其他函数,给你几次添加方法的机会,直到最后都没有添加方法程序就会崩溃。当然,动态添加方法最好的时机就是调用这两个方法的时候。
+ (BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(eat)) {        class_addMethod([self class], sel, (IMP)eat, "v@:");        return YES;    }    return [super resolveInstanceMethod:sel];}void eat(id self,SEL _cmd){    NSLog(@"我已经吃了....");}
  • 函数参数
    • __unsafe_unretained Class cls
      这是向哪个类中添加该方法
    • SEL name
      这是函数名称
    • IMP imp
      这是一个函数指针,指向下面的c函数
    • const char *types
      这是函数类型,每个c函数都有两个隐藏参数(id self,SEL _cmd)一个是方法调用者,一个是方法编号,具体可以看官方帮助文档。
1 0