内容简介:相信有过面试经验的iOS开发者都知道在iOS开发的面试中,runtime是属于必问的几个点之一,但是平时我们工作中接触到它的机会真的少之又少,很多人一直都认为runtime只是用来装13神器而已。但是随着对IOS的深度学习和日常工作中发现runtime真的是相当好用,但是也没有那么神秘不测!runtime 是 OC底层的一套C语言的API(引入 或),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++
相信有过面试经验的iOS开发者都知道在iOS开发的面试中,runtime是属于必问的几个点之一,但是平时我们工作中接触到它的机会真的少之又少,很多人一直都认为runtime只是用来装13神器而已。但是随着对IOS的深度学习和日常工作中发现runtime真的是相当好用,但是也没有那么神秘不测!
什么是runtime?
runtime 是 OC底层的一套 C语言 的API(引入 或),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。比如我们创建了一个对象 [[NSObject alloc]init],最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象。
咱们先来看看OC与C的对比:
1.OC是对OC的面向对象的封装,OC中的对象只是C中指向结构体的指针。 2.OC的方法,本质上就是C语言中的函数,OC中的任意一个方法,在runtime中都会有一个与之对应的函数。
eg:[objc sendMessage:@"I am back"]; -> objc_msg(self,@selector(sendMessage),"I am back");
所以说在OC中对象调用方法,到运行的时候,都会变成向对象发送消息,这就是runtime中最著名的消息机制。
3.既然本质都是函数,那是不是和C语言的函数没有区别呢?绝对不是。 (1)C语言只能调用实现过的函数,只声明了是不行的,编译是不能通过的。 (2)OC无所谓,只要声明了就能调用,即时你没声明都能调用,编译阶段都不会报错,只会报警告。
- (id)performSelector:(SEL)aSelector;
这样据说是保证了编程的灵活性,反正大家都这么说,但是我觉得这就是不够严谨,因为真要是需要这个方法执行了,程序就得崩溃,在编译的时候就能解决的问题,为什么要等到程序崩溃再修改代码呢,有点浪费时间啊。
利用runtime 可以做一些OC不容易实现的功能
1,动态交换两个方法的实现(特别是交换系统自带的方法)
2,动态添加对象的成员变量和成员方法
3,获得某个类的所有成员方法、所有成员变量
如何应用运行时?
1.将某些OC代码转为运行时代码,探究底层,比如block的实现原理(上边已讲到); 2.拦截系统自带的方法调用(Swizzle 黑魔法),比如拦截imageNamed:、viewDidLoad、alloc; 3.实现分类也可以增加属性; 4.实现NSCoding的自动归档和自动解档; 5.实现字典和模型的自动转换。
下面我通过demo 我一个个来讲解
一、交换两个方法的实现,拦截系统自带的方法调用功能
需要用到的方法
获得某个类的类方法
Methodclass_getClassMethod(Classcls , SEL name)
获得某个类的实例对象方法
Methodclass_getInstanceMethod(Classcls , SEL name)
交换两个方法的实现
void method_exchangeImplementations(Methodm1,Methodm2)
#####案例1:方法简单的交换 创建一个Person类,类中实现以下两个类方法,并在.h 文件中声明
- (void)run{ 控制器中调用,则先打印跑,后打印学习 [Personrun]; 下面通过runtime 实现方法交换,类方法用class_getClassMethod,对象方法用class_getInstanceMethod // 获取两个类的类方法
案例2:拦截系统方法需求:
比如iOS6 升级 iOS7 后需要版本适配,根据不同系统使用不同样式图片(拟物化和扁平化),如何通过不去手动一个个修改每个UIImage的imageNamed:方法就可以实现为该方法中加入版本判断语句?
步骤: 1、为UIImage建一个分类(UIImage+Category)
2、在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,比如版本判断
+(UIImage*)xh_imageNamed:(NSString *)name {
3、分类中重写UIImage的load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)
- (void)load {
注意:自定义方法中最后一定要再调用一下系统的方法,让其有加载图片的功能,但是由于方法交换,系统的方法名已经变成了我们自定义的方法名(有点绕,就是用我们的名字能调用系统的方法,用系统的名字能调用我们的方法),这就实现了系统方法的拦截!
利用以上思路,我们还可以给 NSObject 添加分类,统计创建了多少个对象,给控制器添加分类,统计有创建了多少个控制器,特别是公司需求总变的时候,在一些原有控件或模块上添加一个功能,建议使用该方法!
二、在分类中设置属性,给任何一个对象设置属性
众所周知,分类中是无法设置属性的,如果在分类的声明中写@property 只能为其生成get 和 set 方法的声明,但无法生成成员变量,就是虽然点语法能调用出来,但程序执行后会crash,有人会想到使用全局变量呢?比如这样:
int _age;
但是全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就需要借助runtime为分类增加属性的功能了。
需要用到的方法
- set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
- 参数 object:给哪个对象设置属性
- 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
- 参数 value:给属性设置的值
- 参数policy:存储策略 (assign 、copy 、 retain就是strong)
voidobjc_setAssociatedObject(idobject,constvoid*key ,idvalue,objc_AssociationPolicy policy)
利用参数key 将对象object中存储的对应值取出来
idobjc_getAssociatedObject(idobject ,constvoid*key)
步骤: 1、创建一个分类,比如给任何一个对象都添加一个name属性,就是NSObject添加分类(NSObject+Category)
2、先在.h 中@property 声明出get 和 set 方法,方便点语法调用@property(nonatomic,copy)NSString*name;
3、在.m 中重写set 和 get 方法,内部利用runtime 给属性赋值和取值char nameKey;
三、获得一个类的所有成员变量
最典型的用法就是一个对象在归档和解档的 encodeWithCoder和initWithCoder:方法中需要该对象所有的属性进行decodeObjectForKey: 和 encodeObject:,通过runtime我们声明中无论写多少个属性,都不需要再修改实现中的代码了。
需要用到的方法
获得某个类的所有成员变量(outCount 会返回成员变量的总数)
参数:
1、哪个类
2、放一个接收值的地址,用来存放属性的个数
3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型
Ivar class_copyIvarList(Class cls ,unsignedint outCount)
获得成员变量的名字
constchar*ivar_getName(Ivar v)
获得成员变量的类型
constchar*ivar_getTypeEndcoding(Ivar v)
案例1:获取Person类中所有成员变量的名字和类型
unsignedintoutCount =0;
案例2:利用runtime 获取所有属性来重写归档解档方法// 设置不需要归解档的属性
依据上面的原理我们就可以给NSObject做一个分类,让我们不需要每次都写这么一长串代码,只要实现一小段代码就可以让一个对象具有归解档的能力。
注意,下面的代码我换了一个方法名(不然会覆盖系统原来的方法!),加了一个忽略属性方法是否被实现的判断,并加上了对父类属性的归解档循环。
NSObject+Extension.h
#import
NSObject+Extension.m
#import"NSObject+Extension.h"
上面分类使用方法:在需要归解档的对象中实现下面方法即可:
// 设置需要忽略的属性
这样看来,我们每次又要写同样的代码,我们可以将归解档两个方法封装为宏,在需要的地方一句宏搞定,如果有不需要归解档的属性就实现ignoredNames 方法,具体可以看我的demo,这个也是MJExtension中那个一句宏就可以解决归解档的实现原理。
案例3:利用runtime 获取所有属性来进行字典转模型以往我们都是利用KVC进行字典转模型,但是它还是有一定的局限性,例如:模型属性和键值对对应不上会crash(虽然可以重写setValue:forUndefinedKey:方法防止报错),模型属性是一个对象或者数组时不好处理等问题,所以无论是效率还是功能上,利用runtime进行字典转模型都是比较好的选择。
字典转模型我们需要考虑三种特殊情况:
1.当字典的key和模型的属性匹配不上
2.模型中嵌套模型(模型属性是另外一个模型对象)
3.数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)
根据上面的三种特殊情况,我们一个个处理,先是字典的key和模型的属性不对应的情况。
不对应有两种,一种是字典的键值大于模型属性数量,这时候我们不需要任何处理,因为runtime是先遍历模型所有属性,再去字典中根据属性名找对应值进行赋值,多余的键值对也当然不会去看了;另外一种是模型属性数量大于字典的键值对,这时候由于属性没有对应值会被赋值为nil,就会导致crash,我们只需加一个判断即可,JSON数据和sample如下:
-(void)setDict:(NSDictionary *)dict {
这时候我们就需要利用runtime的ivar_getTypeEncoding 方法获取模型对象类型,对该模型对象类型再进行字典转模型,也就是进行递归,需要注意的是我们要排除系统的对象类型,例如NSString,下面的方法中我添加了一个类方法方便递归。
#import"NSObject+JSONExtension.h"
第三种情况是模型的属性是一个数组,数组中是一个个模型对象,例如下面的数据我就可以通过books[0].name
获取到C语言程序设计
我们既然能获取到属性类型,那就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是我们不知道数组中的模型都是什么类型,我们可以声明一个方法,该方法目的不是让其调用,而是让其实现并返回模型的类型。
这块语言可能解释不太清楚,可以参考我的demo,直接运行即可。
NSObject+JSONExtension.h
// 返回数组中都是什么类型的模型对象
NSObject+JSONExtension.m
#import"NSObject+JSONExtension.h"
#小编给大家推荐一个iOS技术交流群: 923910776 !群内提供数据结构与算法、底层进阶、swift、逆向、整合面试题等免费资料!
以上所述就是小编给大家介绍的《2019年最实用的runtime面试总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Inside Larry's and Sergey's Brain
Richard Brandt / Portfolio / 17 Sep 2009 / USD 24.95
You’ve used their products. You’ve heard about their skyrocketing wealth and “don’t be evil” business motto. But how much do you really know about Google’s founders, Larry Page and Sergey Brin? Inside......一起来看看 《Inside Larry's and Sergey's Brain》 这本书的介绍吧!