iOS Runtime(一)

什么是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 = [NSURLRequest
requestWithURL: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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值