Runtime常用的几个应用场景(可以先看上一篇文章)
2017-09-29 16:08
501 查看
Runtime常见应用场景
具体应用拦截系统自带的方法调用(Method Swizzling黑魔法)
实现给分类增加属性
实现字典的模型和自动转换
JSPatch替换已有的OC方法实行等
我们常用Method Swizzling来将系统的方法换为我们自定义的方法,给系统方法添加一些需要的功能,来实现某些需求。例如,跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。再例如更换全局UILabel默认字体,可以通过Method Swizzling替换UILabel初始方法来修改等。
实例
因为Method Swizzling的实现模式比较固定,所以将其抽象为一个分类,可以直接方便调用.
NSObject Category
class_addMethod:实现会覆盖父类的方法实现。但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数返回NO。这里为源SEL添加IMP是为了避免源SEL没有实现IMP的情况。若添加成功则说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP;若添加失败则说明源SEL已经有IMP了,直接将两个SEL的IMP交换就可以了。
class_replaceMethod:该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于clss_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样代替原方法的实现。
实现第一个场景:跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。
创建一个UIViewController的分类,重写自定义的ViewDidAppear方法,并在其+load方法中实现ViewDidAppear方法的交换。
这里有几个注意点:
Swizzling应该总在+load中执行
在OC中,Runtime会在类初始加载时调用+load方法,在类第一次被调用时实现+initialize方法。由于Method Swizzling会影响到类的全局状态,所以要尽量避免在并发处理中出现竞争情况。+load方法能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。
要使用dispatch_once执行方法交换
方法交换要求线程安全,而且保证在任何情况下只能交换一次。
关联对象是Runtime的一个特性,Runtime中定义了三个允许你讲将任何键值在Runtime关联到对象上的函数:
objc_setAssociatedObject
设置关联对象:
object:需要设置关联对象的对象
key:关联对象的key,推荐使用selector
value:关联对象的值,id类型
policy:关联对象的策略,属性可以根据定义在枚举类型objc_AssociationPolicy上的行为被关联在对象上。类似于@property创建时设置的关键字。
objc_getAssociatedObject
获取关联对象
objc_removeAssociatedObjects
移除某个对象的所有关联对象,此方法不常用。
通过提供的方法我们就可以对存在的类在拓展中添加自定义的属性了。
实例:
为UIImage类添加一个downLoadURL的属性。
有名的第三方库LTNavigationBar在实现滑动界面时导航栏显隐功能时也使用了Associated Objects,为UINavigation类添加了一个UIView类的属性overlay,使功能实现起来更加简便。大家可以阅读一下源码。
LTNavigationBar
阅读YYModel的源码可以看出,YY大神对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYClPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。
源码中提取Class的Ivar的相关代码:
其中的运用的Runtime相关知识在上篇已说明,不再赘述。
为了方便大家更好的理解字典转模型,我粗略的写了一个极简陋版的转模型方案供大家理解其思路,更优秀完美的思路建议大家阅读优秀第三方框架的源码。
JSPatch能做到通过JS调用和改写OC方法最根本的原因是OC是动态语言,OC上所有方法的调用和类的生成都通过OC Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。理论上你可以在运行时通过类名/方法名调用任何OC方法,替换任何类的实现以及新增任意类,所以JSPatch的基本原理是:JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。这是最基础的原理。
在JSPatch实现方法替换上,通过Selector调用方法时,会从methodList链表里找到对应的Method进行调用,这个methodList上的元素是可以动态替换的,可以把某个Selector对应的函数指针IMP替换成新的,也可以拿到已有的某个Selector对应的函数指针IMP,让另一个Selector跟它对应,Runtime提供了相应的方法实现这些。
Runtime知识体系庞大,本文仅抛砖引玉,希望对大家有所帮助。
具体应用拦截系统自带的方法调用(Method Swizzling黑魔法)
实现给分类增加属性
实现字典的模型和自动转换
JSPatch替换已有的OC方法实行等
一、Method Swizzling
Method Swizzling是改变一个已存在的selector的实现的技术。可以使用它来在Runtime通过修改类的分发表中selector对应的函数,来修改selector的实现。我们常用Method Swizzling来将系统的方法换为我们自定义的方法,给系统方法添加一些需要的功能,来实现某些需求。例如,跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。再例如更换全局UILabel默认字体,可以通过Method Swizzling替换UILabel初始方法来修改等。
实例
因为Method Swizzling的实现模式比较固定,所以将其抽象为一个分类,可以直接方便调用.
NSObject Category
#import <Foundation/Foundation.h> @interface NSObject (Swizzling) +(void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector; @end #import "NSObject+Swizzling.h" #import <objc/runtime.h> @implementation NSObject (Swizzling) +(void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzleMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzleMethod); } } @end
class_addMethod:实现会覆盖父类的方法实现。但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数返回NO。这里为源SEL添加IMP是为了避免源SEL没有实现IMP的情况。若添加成功则说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP;若添加失败则说明源SEL已经有IMP了,直接将两个SEL的IMP交换就可以了。
class_replaceMethod:该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于clss_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样代替原方法的实现。
实现第一个场景:跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。
创建一个UIViewController的分类,重写自定义的ViewDidAppear方法,并在其+load方法中实现ViewDidAppear方法的交换。
#import <UIKit/UIKit.h> @interface UIViewController (Swizzling) @end #import "UIViewController+Swizzling.h" #import "NSObject+Swizzling.h" @implementation UIViewController (Swizzling) +(void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [UIViewController methodSwizzlingWithOriginalSelector:@selector(viewDidAppear:) bySwizzledSelector:@selector(my_ViewDidAppear:)]; }); } -(void) my_ViewDidAppear:(BOOL)animated{ [self my_ViewDidAppear:animated]; NSLog(@"===== %@ viewDidAppear=====",[self class]); } @end
这里有几个注意点:
Swizzling应该总在+load中执行
在OC中,Runtime会在类初始加载时调用+load方法,在类第一次被调用时实现+initialize方法。由于Method Swizzling会影响到类的全局状态,所以要尽量避免在并发处理中出现竞争情况。+load方法能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。
要使用dispatch_once执行方法交换
方法交换要求线程安全,而且保证在任何情况下只能交换一次。
二、实现给分类增加属性
开发中常需要在不改变某个类的前提下为其添加一个新的属性,尤其是为系统的类添加新的属性,这个时候就可以利用Runtime的关联对象(Associated Objects)来为分类添加新的属性了。关联对象是Runtime的一个特性,Runtime中定义了三个允许你讲将任何键值在Runtime关联到对象上的函数:
objc_setAssociatedObject
设置关联对象:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
object:需要设置关联对象的对象
key:关联对象的key,推荐使用selector
value:关联对象的值,id类型
policy:关联对象的策略,属性可以根据定义在枚举类型objc_AssociationPolicy上的行为被关联在对象上。类似于@property创建时设置的关键字。
objc_getAssociatedObject
获取关联对象
id objc_getAssociatedObject(id object, const void *key)
objc_removeAssociatedObjects
移除某个对象的所有关联对象,此方法不常用。
通过提供的方法我们就可以对存在的类在拓展中添加自定义的属性了。
实例:
为UIImage类添加一个downLoadURL的属性。
#import <UIKit/UIKit.h> @interface UIImage (downLoadURL) @property (nonatomic, strong) NSString *downLoadURL; @end #import "UIImage+downLoadURL.h" #import <objc/runtime.h> @implementation UIImage (downLoadURL) -(void)setDownLoadURL:(NSString *)downLoadURL{ objc_setAssociatedObject(self, @selector(downLoadURL), downLoadURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -(NSString *)downLoadURL{ return objc_getAssociatedObject(self, @selector(downLoadURL)); } @end
有名的第三方库LTNavigationBar在实现滑动界面时导航栏显隐功能时也使用了Associated Objects,为UINavigation类添加了一个UIView类的属性overlay,使功能实现起来更加简便。大家可以阅读一下源码。
LTNavigationBar
三、实现字典的模型和自动转换
优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。阅读YYModel的源码可以看出,YY大神对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYClPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。
源码中提取Class的Ivar的相关代码:
unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); }
其中的运用的Runtime相关知识在上篇已说明,不再赘述。
为了方便大家更好的理解字典转模型,我粗略的写了一个极简陋版的转模型方案供大家理解其思路,更优秀完美的思路建议大家阅读优秀第三方框架的源码。
@interface NSObject (ZCModel) +(instancetype) setModelWithDict:(NSDictionary*)dict; @end @implementation NSObject (ZCModel) +(instancetype) setModelWithDict:(NSDictionary*)dict{ Class cls = [self class]; id Model = [[self alloc]init]; unsigned int count; //提取Class的property列表 objc_property_t *property_t = class_copyPropertyList(cls, &count); //遍历列表,对每个property分别处理 for (int i =0; i< count; i++) { objc_property_t property = property_t[i]; NSString *key = [NSString stringWithUTF8String:property_getName(property)]; id value = dict[key]; if (!value) continue; //提取property的attribute信息 NSString *attribure = [NSString stringWithUTF8String:property_getAttributes(property)]; //从attribute信息中提取其class的信息 if ([attribure hasPrefix:@"T@"]) { NSRange range = [attribure rangeOfString:@"\","]; NSString *typeString = [attribure substringWithRange:NSMakeRange(3, range.location - 3)]; NSLog(@"the property class is %@",typeString); //对非NS开头的class处理为嵌套的model,对model进行递归,转为模型 if (![typeString hasPrefix:@"NS"]) { Class modelClass = NSClassFromString(typeString); value = [modelClass setModelWithDict:value]; } } //将字典中的值设置给模型 [Model setValue:value forKeyPath:key]; } free(property_t); return Model; } @end
四、JSPatch替换已有的OC方法实行
JSPatch是个优秀开源项目,只需要在项目里引入极小的引擎文件就可以使用JavaScript调用任何OC的原生接口,替换任意的OC原生方法。目前主要用于下发JS脚本替换原生OC代码,实时修复线上bug,更多详情可以阅读JSPatch技术文档。JSPatch能做到通过JS调用和改写OC方法最根本的原因是OC是动态语言,OC上所有方法的调用和类的生成都通过OC Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。理论上你可以在运行时通过类名/方法名调用任何OC方法,替换任何类的实现以及新增任意类,所以JSPatch的基本原理是:JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。这是最基础的原理。
在JSPatch实现方法替换上,通过Selector调用方法时,会从methodList链表里找到对应的Method进行调用,这个methodList上的元素是可以动态替换的,可以把某个Selector对应的函数指针IMP替换成新的,也可以拿到已有的某个Selector对应的函数指针IMP,让另一个Selector跟它对应,Runtime提供了相应的方法实现这些。
总结
通过以上四个实例可以了解到Runtime在我们实际开发中常用的几个场景,Runtime强大的功能是一把双刃剑,一方面它涉及许多底层实现,如果使用不善很可能造成许多问题,所以在开发中要谨慎仔细的使用;但另一方面,Runtime底层功能让我们可以实现类的动态配置、消息传递和方法替换等功能,实现项目需求,提高开发效率,更好的理解OC这门动态语言。Runtime知识体系庞大,本文仅抛砖引玉,希望对大家有所帮助。
相关文章推荐
- Runtime常用的几个应用场景(可以先看runtime基础学习篇)
- zookeeper原理讲解(讲的很深奥不易懂 原理可以看上一篇 应用场景可以看看)
- Runtime常用的几个应用场景
- 一篇文章让你彻底明白区块链有哪些应用场景
- 发布了每日一文应用,每日一文,每天阅读,简单生活。每天精选一篇不同的文章,也可以随机阅读往日精选文章
- threadlocal原理及常用应用场景
- 一篇介绍标签库很详细的文章,可以做为参考手册
- 一篇很经典的文章,建议工作2年以上的朋友都可以看看
- 一篇很好的文章,学verilog的可以好好看看!(转载)
- 几个常用算法的适应场景及其优缺点(非常好)
- [转]转一篇不错的文章:ORM 在电子商务系统中的应用
- 今天看到一篇很好的文章,介绍了html5常用几种api
- Redis常用数据类型应用场景
- 几个常用的android应用上架平台
- runtime的一篇文章
- Microsoft Sync Framework Runtime 简介(一篇非常好的MSF介绍文章)
- java应用集锦9:httpclient4.2.2的几个常用方法,登录之后访问页面问题,下载文件
- 转载一篇关于ALE的应用极好的文章
- grid基础语法介绍(上) 《轴线与网格》里主要讲述了grid与flex中,网格与轴线的基本概念,了解了这些基本概念之后,我们可以更轻松地对布局方式进行研究,这一篇文章主要描述grid布局中,定义在容
- 听说此文又是一篇找砖头的文章, 善良,可爱型的人请速速离开。 1: 模板引擎。 模板引擎是否得继续使用及发挥作用, 成为phper们在争论的第一话题。 其实我们可以换个立场来思考, 如果d