Runtime常见的几个应用场景。

  • Runtime常见应用场景

    • 具体应用拦截系统自带的方法调用(Method Swizzling黑魔法)
    • 实现给分类增加属性
    • 实现字典的模型和自动转换
    • JSPatch替换已有的OC方法实行等

一、具体应用拦截系统自带的方法调用  (Method Swizzling)

Method Swizzling是改变一个已存在的selector的实现的技术。可以使用它来在Runtime通过修改类的分发表中selector对应的函数,来修改selector的实现。
我们常用Method Swizzling来将系统的方法换为我们自定义的方法,给系统方法添加一些需要的功能,来实现某些需求。

1.例如,跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。

2.再例如更换全局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执行方法交换
方法交换要求线程安全,而且保证在任何情况下只能交换一次。

//例如:image 的方法交换

#import "UIImage+swizzing.h"

@implementation UIImage (swizzing)

+ (void)load{

NSLog(@"imae laod");

Method oldMethod = class_getClassMethod(self, @selector(imageNamed:));

Method newMethod = class_getClassMethod(self, @selector(sw_imageWithName:));

method_exchangeImplementations(oldMethod, newMethod);

}

+ (UIImage *)sw_imageWithName:(NSString *)imageName{

// 这里调用sw_imageWithName,相当于调用imageName

UIImage *image = [UIImage sw_imageWithName:imageName];

if (!image) {

NSLog(@"imgae nil");

}

NSLog(@"new method");

return image;

}

@end

二、实现给 分类增加属性

开发中常需要在不改变某个类的前提下为其添加一个新的属性,尤其是为系统的类添加新的属性,这个时候就可以利用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提供了相应的方法实现这些。

五、获取类的所有属性/方法名称

#import "TestRuntimeModel.h"

#import <objc/runtime.h>

- (void)getAllProperty {

unsigned int count = 0;

Ivar *allVariables = class_copyIvarList([TestRuntimeModel class], &count);

for (int i = 0; i<count; i++) {

Ivar ivar = allVariables[i];

const char *varName = ivar_getName(ivar);

const char *varType = ivar_getTypeEncoding(ivar);

NSLog(@"--%s--%s--",varName,varType);

}

}

- (void)getAllMethod {

unsigned int count = 0;

Method *allMethods = class_copyMethodList([TestRuntimeModel class], &count);

for (int i = 0; i<count; i++) {

Method method = allMethods[i];

SEL sel = method_getName(method);

const char *menthName = sel_getName(sel);

NSLog(@"--%s--",menthName);

}

}

总结

通过以上四个实例可以了解到Runtime在我们实际开发中常用的几个场景,Runtime强大的功能是一把双刃剑,一方面它涉及许多底层实现,如果使用不善很可能造成许多问题,所以在开发中要谨慎仔细的使用;但另一方面,Runtime底层功能让我们可以实现类的动态配置、消息传递和方法替换等功能,实现项目需求,提高开发效率,更好的理解OC这门动态语言。

Runtime 实现对象归档

实现<NSCoding>协议

//获取所有属性

- (NSArray *)propertyOfSelf{

unsigned int count = 0;

Ivar *ivarList = class_copyIvarList([self class], &count);

NSMutableArray *propertyArr = [NSMutableArray array];

for (int i = 0; i<count; i++) {

//获取成员属性

Ivar ivar = ivarList[i];

//属性名

NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

//去掉属性名前面的_

NSString *key = [name substringFromIndex:1];

[propertyArr addObject:key];

}

return [propertyArr copy];

}

//归档

- (void)encodeWithCoder:(NSCoder *)aCoder{

NSArray *propertyNames = [self propertyOfSelf];

for (NSString *properName in propertyNames) {

id value = [self valueForKey:properName];//KVC

[aCoder encodeObject:value forKey:properName];

}

}

//解档

- (instancetype)initWithCoder:(NSCoder *)coder

{

self = [super init];

if (self) {

NSArray *propertyNames = [self propertyOfSelf];

for (NSString *properName in propertyNames) {

id value = [coder decodeObjectForKey:properName];

[self setValue:value forKey:properName];//KVC

}

}

return self;

}

//使用

- (IBAction)arch:(id)sender {

Student *pp = [Student new];

pp.name = @"xiaoming";

pp.age = @"11";

NSLog(@"Name:%@--%@",pp.name,pp.age);

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:pp];

[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"sss"];

}

- (IBAction)unarch:(id)sender {

NSData *data = [[NSUserDefaults standardUserDefaults]objectForKey:@"sss"];

Student *stu = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSLog(@"Name:%@--%@",stu.name,stu.age);

}

//传统写法(属性较少的情况)

  1. #pragma mark - NSCoding
  2. - (void)encodeWithCoder:(NSCoder *)aCoder {
  3. [aCoder encodeObject:_name forKey:kNameKey];
  4. [aCoder encodeInteger:_age forKey:kAgeKey];
  5. }
  6. - (id)initWithCoder:(NSCoder *)aDecoder {
  7. if (self = [super init]) {
  8. _name = [aDecoder decodeObjectForKey:kNameKey];
  9. _age = [aDecoder decodeIntegerForKey:kAgeKey];
  10. }
  11. return self;
  12. }
  13. #pragma mark - NSCoping
  14. - (id)copyWithZone:(NSZone *)zone {
  15. Archive *copy = [[[self class] allocWithZone:zone] init];
  16. copy.name = [self.name copyWithZone:zone];
  17. copy.age = self.age;
  18. return copy;
  19. }

Runtime常用的几个应用场景的更多相关文章

  1. Redis常用数据类型介绍、使用场景及其操作命令

    Redis常用数据类型介绍.使用场景及其操作命令 本文章同时也在cpper.info发布. Redis目前支持5种数据类型,分别是: 1.String(字符串) 2.List(列表) 3.Hash(字 ...

  2. iOS开发之Runtime常用示例总结

    经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真没正儿八经的总结过.之前只是在解析第三方框架源码时,聊过一些用法,也就是这些第三方框架中用到的Runt ...

  3. Runtime常用的几个场景

    1.给分类动态添加属性 在FDFullscreenPopGesture中给UIViewController的分类里有这么一个属性: @property (nonatomic, copy) _FDVie ...

  4. Runtime简介以及常见的使用场景(此内容非原创,为转载内容)

    Runtime简称运行时,是一套比较底层的纯C语言的API, 作为OC的核心,运行时是一种面向对象的编程语言的运行环境,其中最主要的是消息机制,Objective-C 就是基于运行时的. 所谓运行时, ...

  5. Jmeter常用线程组设置及场景运行时间计算

    Jmeter中通过线程组来模拟大用户并发场景,今天主要介绍三个常用的线程组,帮助我们设计更加完善的测试场景,另外介绍下场景执行时间如何计算. 一.Thread Group 取样器错误后要执行的动作   ...

  6. 常用的 Git 命令与场景

    Git 分布式版本控制系统 它拥有完整的版本控制功能,能够解决多人协作的问题 将自己的代码同步到 Github 上能够提升开发效率 git 会记录你每一次的版本修改操作 常用的 Git 操作 # 指定 ...

  7. Jmeter常用的两大性能测试场景

    一.阶梯式场景 该场景主要应用在负载测试里面,通过设定一定的并发线程数,给定加压规则,遵循“缓起步,快结束”的原则,不断地增加并发用户来找到系统的性能瓶颈,进而有针对性的进行各方面的系统优化. 使用到 ...

  8. 常用的SQL查询思维/场景

    前言 现在大多数开发工作中,已经可以使用一些组件或框架提供的强大的条件构造器来完成查询数据了,虽然强大而且方便,但也还是存在很多业务场景需要实打实的编写传统SQL语句.特别一些测试.维护.问题排查的时 ...

  9. Runtime相关整理

    一.Runtime是用C和汇编写的 对于C语言,函数的调用在编译的时候会决定调用哪个函数.对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名 ...

随机推荐

  1. Dev控件GridControl实现CheckBox列和ComBox列

    1.在sql语句中添加空白行,如select c1,c2 null c3 from xxx; 2.将sql语句查询结果与gdc绑定CmmFrm.BestFitGridViewColumnsWidth( ...

  2. ASP.NET——配置文件——连接字符串

    一.连接字符串 1.通过<connectionStrings>方式: 方式一:SqlServer身份验证 <connectionStrings> <add name=&q ...

  3. 类型:sqlserver;问题:版本;结果:sqlserver版本区分

    LocalDB (SqlLocalDB)LocalDB 是 Express 的一种轻型版本,该版本具备所有可编程性功能,但在用户模式下运行,并且具有快速的零配置安装和必备组件要求较少的特点.如果您需要 ...

  4. #测试两种不同的SVM,rbf的核真是太棒了(一种会拐弯的边界)

    from sklearn import datasets import numpy as np X, y = datasets.make_blobs(n_features=2, centers=2) ...

  5. LAMP 2.9 php扩展模块如何安装

    php 和 apache 类似,核心文件为/usr/local/php/bin/php,针对 apache 的是/usr/local/apache2/modules/libphp5.so 模块.这两个 ...

  6. object类型对象 ref参数如何理解?

    class Program { static void Main(string[] args) { Student stu = new Student { Name = "老王" ...

  7. Codeforces 1097F Alex and a TV Show (莫比乌斯反演)

    题意:有n个可重集合,有四种操作: 1:把一个集合设置为单个元素v. 2:两个集合求并集. 3:两个集合中的元素两两求gcd,然后这些gcd形成一个集合. 4:问某个可重复集合的元素v的个数取模2之后 ...

  8. 使用ServerSocket建立聊天服务器(二)

    -------------siwuxie095                         工程名:TestMyServerSocket 包名:com.siwuxie095.socket 类名:M ...

  9. cocos2d-js 热更新模块 使用AssetsManager

    原帖子地址:http://cn.cocos2d-x.org/tutorial/show?id=1186 在这个文章中原作者已经说的很清楚,我在这个其他改动一些适用我项目中需求 1.满足Web和Nati ...

  10. 71-n皇后

    N皇后问题 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...