iOS面试中经常问的点 - RunTime

RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。

对于C语言,函数的调用在编译的时候会决定调用哪个函数,如果调用未实现的函数就会报错。 对于OC语言,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!关注作者;需要ios进阶视频资料,其他文章可以找到我

二. RunTime消息机制

消息机制是运行时里面最重要的机制,OC中任何方法的调用,本质都是发送消息。 使用运行时,发送消息需要导入框架  并且xcode5之后,苹果不建议使用底层方法,如果想要使用运行时,需要关闭严格检查objc_msgSend的调用,BuildSetting->搜索msg 改为NO。

下来看一下实例方法调用底层实现

Person *p = [[Person alloc] init];[p eat];// 底层会转化成//SEL:方法编号,根据方法编号就可以找到对应方法的实现。[p performSelector:@selector(eat)];//performSelector本质即为运行时,发送消息,谁做事情就调用谁 objc_msgSend(p,@selector(eat));// 带参数objc_msgSend(p,@selector(eat:),10);

类方法的调用底层

// 本质是会将类名转化成类对象,初始化方法其实是在创建类对象。[Person eat];// Person只是表示一个类名,并不是一个真实的对象。只要是方法必须要对象去调用。// RunTime 调用类方法同样,类方法也是类对象去调用,所以需要获取类对象,然后使用类对象去调用方法。Class personclass = [Persionclass];[[Persionclass]performSelector:@selector(eat)];// 类对象发送消息objc_msgSend(personclass,@selector(eat));

**@selector (SEL):是一个SEL方法选择器。**SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。 对于一个类中。每一个方法对应着一个SEL。所以一个类中不能存在2个名称相同的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。

运行时发送消息的底层实现每一个类都有一个方法列表 Method List,保存这类里面所有的方法,根据SEL传入的方法编号找到方法,相当于value - key的映射。然后找到方法的实现。去方法的实现里面去实现。如图所示。

那么内部是如何动态查找对应的方法的?首先我们知道所有的类中都继承自NSObject类,在NSObjcet中存在一个Class的isa指针。

typedefstructobjc_class*Class;@interface NSObject {    Class isa  OBJC_ISA_AVAILABILITY;}

我们来到objc_class中查看,其中包含着类的一些基本信息。

structobjc_class{  Class isa;// 指向metaclassClass super_class ;// 指向其父类constchar*name ;// 类名long version ;// 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取long info;// 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;long instance_size ;// 该类的实例变量大小(包括从父类继承下来的实例变量);structobjc_ivar_list*ivars;// 用于存储每个成员变量的地址structobjc_method_list**methodLists ;// 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;structobjc_cache*cache;// 指向最近使用的方法的指针,用于提升效率;structobjc_protocol_list*protocols;// 存储该类遵守的协议}

下面我们就以p实例的eat方法来看看具体消息发送之后是怎么来动态查找对应的方法的。

实例方法 [p eat]; 底层调用 [p performSelector:@selector(eat)]; 方法,编译器在将代码转化为 objc_msgSend(p, @selector(eat));

在 objc_msgSend 函数中。首先通过 p 的 isa 指针找到 p 对应的 class 。在 Class 中先去 cache 中通过 SEL 查找对应函数 method ,如果找到则通过 method 中的函数指针跳转到对应的函数中去执行。

若 cache 中未找到。再去 methodList 中查找。若能找到,则将 method 加入到 cache 中,以方便下次查找,并通过 method 中的函数指针跳转到对应的函数中去执行。

若 methodlist 中未找到,则去 superClass 中查找。若能找到,则将 method 加入到 cache中,以方便下次查找,并通过 method 中的函数指针跳转到对应的函数中去执行。

三. 使用RunTime交换方法:

当系统自带的方法功能不够,需要给系统自带的方法扩展一些功能,并且保持原有的功能时,可以使用RunTime交换方法实现。这里要实现image添加图片的时候,自动判断image是否为空,如果为空则提醒图片不存在。 方法一:使用分类

+ (nullableUIImage*)xx_ccimageNamed:(NSString*)name{// 加载图片    如果图片不存在则提醒或发出异常UIImage*image = [UIImageimageNamed:name];if(image ==nil) {NSLog(@"图片不存在");    }returnimage;}

缺点:每次使用都需要导入头文件,并且如果项目比较大,之前使用的方法全部需要更改。

方法二 :RunTime交换方法交换方法的本质其实是交换两个方法的实现,即调换xx_ccimageNamed和imageName方法,达到调用xx_ccimageNamed其实就是调用imageNamed方法的目的

那么首先需要明白方法在哪里交换,因为交换只需要进行一次,所以在分类的load方法中,当加载分类的时候交换方法即可。

+(void)load{// 获取要交换的两个方法// 获取类方法  用Method 接受一下// class :获取哪个类方法 // SEL :获取方法编号,根据SEL就能去对应的类找方法。Method imageNameMethod = class_getClassMethod([UIImageclass],@selector(imageNamed:));// 获取第二个类方法Method xx_ccimageNameMrthod = class_getClassMethod([UIImageclass],@selector(xx_ccimageNamed:));// 交换两个方法的实现 方法一 ,方法二。method_exchangeImplementations(imageNameMethod, xx_ccimageNameMrthod);// IMP其实就是 implementation的缩写:表示方法实现。}

交换方法内部实现:

根据SEL方法编号在Method中找到方法,两个方法都找到

交换方法的实现,指针交叉指向。如图所示:

注意:交换方法时候 xx_ccimageNamed方法中就不能再调用imageNamed方法了,因为调用imageNamed方法实质上相当于调用 xx_ccimageNamed方法,会循环引用造成死循环。

RunTime也提供了获取对象方法和方法实现的方法。

// 获取方法的实现class_getMethodImplementation(<#__unsafe_unretainedClasscls#>, <#SELname#>)// 获取对象方法class_getInstanceMethod(<#__unsafe_unretainedClasscls#>, <#SELname#>)

此时,当调用imageNamed:方法的时候就会调用xx_ccimageNamed:方法,为image添加图片,并判断图片是否存在,如果不存在则提醒图片不存在。

四. 动态添加方法

如果一个类方法非常多,其中可能许多方法暂时用不到。而加载类方法到内存的时候需要给每个方法生成映射表,又比较耗费资源。此时可以使用RunTime动态添加方法

动态给某个类添加方法,相当于懒加载机制,类中许多方法暂时用不到,那么就先不加载,等用到的时候再去加载方法。

动态添加方法的方法: 首先我们先不实现对象方法,当调用performSelector: 方法的时候,再去动态加载方法。 这里同上创建Person类,使用performSelector: 调用Person类对象的eat方法。

Person *p = [[Person alloc]init];// 当调用 P中没有实现的方法时,动态加载方法[p performSelector:@selector(eat)];

此时编译的时候是不会报错的,程序运行时才会报错,因为Person类中并没有实现eat方法,当去类中的Method List中发现找不到eat方法,会报错找不到eat方法。

而当找不到对应的方法时就会来到拦截调用,在找不到调用的方法程序崩溃之前调用的方法。 当调用了没有实现的对象方法的时,就会调用** +(BOOL)resolveInstanceMethod:(SEL)sel 方法。 当调用了没有实现的类方法的时候,就会调用 +(BOOL)resolveClassMethod:(SEL)sel **方法。

首先我们来到API中看一下苹果的说明,搜索 Dynamic Method Resolution 来到动态方法解析。

Dynamic Method Resolution的API中已经讲解的很清晰,我们可以实现方法 resolveInstanceMethod: 或者 resolveClassMethod: 方法,动态的给实例方法或者类方法添加方法和方法实现。

所以通过这两个方法就可以知道哪些方法没有实现,从而动态添加方法。参数sel即表示没有实现的方法。

一个objective - C方法最终都是一个C函数,默认任何一个方法都有两个参数。 self : 方法调用者 _cmd : 调用方法编号。我们可以使用函数class_addMethod为类添加一个方法以及实现。

这里仿照API给的例子,动态的为P实例添加eat对象

+(BOOL)resolveInstanceMethod:(SEL)sel{// 动态添加eat方法// 首先判断sel是不是eat方法 也可以转化成字符串进行比较。    if(sel ==@selector(eat)) {/**

    第一个参数: cls:给哪个类添加方法

    第二个参数: SEL name:添加方法的编号

    第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)

    第四个参数: types :方法类型,需要用特定符号,参考API

    */class_addMethod(self, sel, (IMP)eat ,"v@:");// 处理完返回YESreturnYES;    }return[superresolveInstanceMethod:sel];}

重点来看一下class_addMethod方法

class_addMethod(__unsafe_unretainedClasscls, SELname, IMP imp,constchar *types)

class_addMethod中的四个参数。第一,二个参数比较好理解,重点是第三,四个参数。

cls : 表示给哪个类添加方法,这里要给Person类添加方法,self即代表Person。

SEL name : 表示添加方法的编号。因为这里只有一个方法需要动态添加,并且之前通过判断确定sel就是eat方法,所以这里可以使用sel。

IMP imp : 表示方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)需要自己来实现这个函数。每一个方法都默认带有两个隐式参数 self : 方法调用者 _cmd : 调用方法的标号 ,可以写也可以不写。

voideat(idself,SEL _cmd){// 实现内容NSLog(@"%@的%@方法动态实现了",self,NSStringFromSelector(_cmd));}

types : 表示方法类型,需要用特定符号。系统提供的例子中使用的是** "v@:" ,我们来到API中看看 "v@:" **指定的方法是什么类型的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值