您好,欢迎访问三七文档
当前位置:首页 > 办公文档 > 总结/报告 > OC最实用的runtime总结,面试工作你看我就足够了
OC最实用的runtime总结,面试、工作你看我就足够了!前言runtime的资料网上有很多了,部分有些晦涩难懂,我通过自己的学习方法总结一遍,主要讲一些常用的方法功能,以实用为主,我觉得用到印象才是最深刻的,并且最后两个demo也是MJExtension的实现原理,面试的时候也可以多扯点。另外runtime的知识还有很多,想要了解更多可以看我翻译的官方文档(有点枯燥),本文的demo下载地址什么是runtime?runtime是OC底层的一套C语言的API(引入objc/runtime.h或objc/message.h),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m文件:clang-rewrite-objcxxx.m可以看到编译后的xxx.cpp(C++文件)。比如我们创建了一个对象[[NSObjectalloc]init],最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象.cpp文件删除掉一些强制转换语句,可以看到调用方法本质就是发消息,[[NSObjectalloc]init]语句发了两次消息,第一次发了alloc消息,第二次发送init消息。利用这个功能我们可以探究底层,比如block的实现原理。需要注意的是,使用objc_msgSend()sel_registerName()方法需要导入头文件objc/message.h消息机制另外利用runtime可以做一些OC不容易实现的功能动态交换两个方法的实现(特别是交换系统自带的方法)动态添加对象的成员变量和成员方法获得某个类的所有成员方法、所有成员变量如何应用运行时?1.将某些OC代码转为运行时代码,探究底层,比如block的实现原理(上边已讲到);2.拦截系统自带的方法调用(Swizzle黑魔法),比如拦截imageNamed:、viewDidLoad、alloc;3.实现分类也可以增加属性;4.实现NSCoding的自动归档和自动解档;5.实现字典和模型的自动转换。下面我通过demo我一个个来讲解一、交换两个方法的实现,拦截系统自带的方法调用功能需要用到的方法objc/runtime.h获得某个类的类方法Methodclass_getClassMethod(Classcls,SELname)获得某个类的实例对象方法Methodclass_getInstanceMethod(Classcls,SELname)交换两个方法的实现voidmethod_exchangeImplementations(Methodm1,Methodm2)案例1:方法简单的交换创建一个Person类,类中实现以下两个类方法,并在.h文件中声明+(void)run{NSLog(@跑);}+(void)study{NSLog(@学习);}控制器中调用,则先打印跑,后打印学习[Personrun];[Personstudy];下面通过runtime实现方法交换,类方法用class_getClassMethod,对象方法用class_getInstanceMethod//获取两个类的类方法Methodm1=class_getClassMethod([Personclass],@selector(run));Methodm2=class_getClassMethod([Personclass],@selector(study));//开始交换方法实现method_exchangeImplementations(m1,m2);//交换后,先打印学习,再打印跑![Personrun];[Personstudy];案例2:拦截系统方法需求:比如iOS6升级iOS7后需要版本适配,根据不同系统使用不同样式图片(拟物化和扁平化),如何通过不去手动一个个修改每个UIImage的imageNamed:方法就可以实现为该方法中加入版本判断语句?步骤:1、为UIImage建一个分类(UIImage+Category)2、在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,比如版本判断+(UIImage*)xh_imageNamed:(NSString*)name{doubleversion=[[UIDevicecurrentDevice].systemVersiondoubleValue];if(version=7.0){//如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片name=[namestringByAppendingString:@_os7];}return[UIImagexh_imageNamed:name];}3、分类中重写UIImage的load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)+(void)load{//获取两个类的类方法Methodm1=class_getClassMethod([UIImageclass],@selector(imageNamed:));Methodm2=class_getClassMethod([UIImageclass],@selector(xh_imageNamed:));//开始交换方法实现method_exchangeImplementations(m1,m2);}注意:自定义方法中最后一定要再调用一下系统的方法,让其有加载图片的功能,但是由于方法交换,系统的方法名已经变成了我们自定义的方法名(有点绕,就是用我们的名字能调用系统的方法,用系统的名字能调用我们的方法),这就实现了系统方法的拦截!利用以上思路,我们还可以给NSObject添加分类,统计创建了多少个对象,给控制器添加分类,统计有创建了多少个控制器,特别是公司需求总变的时候,在一些原有控件或模块上添加一个功能,建议使用该方法!二、在分类中设置属性,给任何一个对象设置属性众所周知,分类中是无法设置属性的,如果在分类的声明中写@property只能为其生成get和set方法的声明,但无法生成成员变量,就是虽然点语法能调用出来,但程序执行后会crash,有人会想到使用全局变量呢?比如这样:int_age;-(int)age{return_age;}-(void)setAge:(int)age{_age=age;}但是全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就需要借助runtime为分类增加属性的功能了。需要用到的方法objc/runtime.hset方法,将值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_AssociationPolicypolicy)利用参数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给属性赋值和取值charnameKey;-(void)setName:(NSString*)name{//将某个值跟某个对象关联起来,将某个值存储到某个对象中objc_setAssociatedObject(self,&nameKey,name,OBJC_ASSOCIATION_COPY_NONATOMIC);}-(NSString*)name{returnobjc_getAssociatedObject(self,&nameKey);}三、获得一个类的所有成员变量最典型的用法就是一个对象在归档和解档的encodeWithCoder和initWithCoder:方法中需要该对象所有的属性进行decodeObjectForKey:和encodeObject:,通过runtime我们声明中无论写多少个属性,都不需要再修改实现中的代码了。需要用到的方法objc/runtime.h获得某个类的所有成员变量(outCount会返回成员变量的总数)参数:1、哪个类2、放一个接收值的地址,用来存放属性的个数3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型Ivar*class_copyIvarList(Classcls,unsignedint*outCount)获得成员变量的名字constchar*ivar_getName(Ivarv)获得成员变量的类型constchar*ivar_getTypeEndcoding(Ivarv)案例1:获取Person类中所有成员变量的名字和类型unsignedintoutCount=0;Ivar*ivars=class_copyIvarList([Personclass],&outCount);//遍历所有成员变量for(inti=0;ioutCount;i++){//取出i位置对应的成员变量Ivarivar=ivars[i];constchar*name=ivar_getName(ivar);constchar*type=ivar_getTypeEncoding(ivar);NSLog(@成员变量名:%s成员变量类型:%s,name,type);}//注意释放内存!free(ivars);案例2:利用runtime获取所有属性来重写归档解档方法//设置不需要归解档的属性-(NSArray*)ignoredNames{return@[@_aaa,@_bbb,@_ccc];}//解档方法-(instancetype)initWithCoder:(NSCoder*)aDecoder{if(self=[superinitWithCoder:aDecoder]){//获取所有成员变量unsignedintoutCount=0;Ivar*ivars=class_copyIvarList([selfclass],&outCount);for(inti=0;ioutCount;i++){Ivarivar=ivars[i];//将每个成员变量名转换为NSString对象类型NSString*key=[NSStringstringWithUTF8String:ivar_getName(ivar)];//忽略不需要解档的属性if([[selfignoredNames]containsObject:key]){continue;}//根据变量名解档取值,无论是什么类型idvalue=[aDecoderdecodeObjectForKey:key];//取出的值再设置给属性[selfsetValue:valueforKey:key];//这两步就相当于以前的self.age=[aDecoderdecodeObjectForKey:@_age];}free(ivars);}returnself;}//归档调用方法-(void)encodeWithCoder:(NSCoder*)aCoder{//获取所有成员变量unsignedintoutCount=0;Ivar*ivars=class_copyIvarList(
本文标题:OC最实用的runtime总结,面试工作你看我就足够了
链接地址:https://www.777doc.com/doc-2884413 .html