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

所谓运行时,是指尽可能地把决定从编译期推迟到运行期,就是尽可能地做到动态.只是在运行的时候才会去确定对象的类型和方法的.因此利用Runtime机制可以在程序运行时动态地修改类和对象中的所有属性和方法。

对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是

①通过 Objective-C 源代码;

②通过 Foundation 框架的NSObject类定义的方法;

③通过对 Runtime 函数的直接调用(需要导入#import <objc/runtime.h>);

大部分情况下只管写OC代码就行,因为OC底层默认实现Runtime,每一个OC的方法,底层必然有一个与之对应的Runtime方法。。

以下是Runtime的一些使用场景:

1.发送消息

方法调用的本质,就是让对象发送消息。objc_msgSend,只有对象才能发送消息

举个简单的例子:如下

().调用对象方法:
// 创建Dog对象
Dog *dog = [[Dog alloc] init]; // 调用对象方法
[dog run];
// 调用对象本质:让对象发送消息
objc_msgSend(dog, @selector(run)); //(2).调用类方法方式:
//有两种
// 第一种通过类名调用
[Dog run];
// 第二种通过类对象调用
[[Dog class] run];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 调用类方法本质:让类对象发送消息
objc_msgSend([Dog class], @selector(run));

消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

2.动态添加方法

开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。简单使用

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. Person *p = [[Person alloc] init]; // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)]; } @end

在person类.m中:

@implementation Person
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
} // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{ if (sel == @selector(eat)) {
// 动态添加eat方法 // 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:"); } return [super resolveInstanceMethod:sel];
}
@end

注意:

+ (BOOL) resolveInstanceMethod:(SEL)aSEL 这个函数与forwardingTargetForSelector类似,都会在对象不能接受某个selector时触发,执行起来略有差别。前者的目的主要在于给客户一个机会来向该对象添加所需的selector,后者的目的在于允许用户将selector转发给另一个对象。另外触发时机也不完全一 样,该函数是个类函数,在程序刚启动,界面尚未显示出时,就会被调用。

在类不能处理某个selector的情况下,如果类重载了该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector 就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用 forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受该 selector的对象,那么resolveInstanceMethod会再次被触发,这一次,如果仍然不添加selector,程序就会报异常

3.运行时关联对象提高效率,给分类添加属性。

使用的时候与懒加载的特点相似,从`关联对象`中获取对象属性,如果有,直接返回。

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. // 给系统NSObject类动态添加属性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"旺财";
NSLog(@"%@",objc.name);
} @end
// 定义关联的key
static const char *key = "name"; @implementation NSObject (Property) - (NSString *)name
{
// 根据关联的key,获取关联的值。
NSString*name = objc_getAssociatedObject(self, key);
if (name!= nil) {
return name;
}
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} @end

4.使用运行时字典转模型

大体思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。(所有字典转模型框架的核心算法)

创建NSObject的分类Runtime:

在.h中的类方法如下:
#import <Foundation/Foundation.h> @interface NSObject (Runtime) /// 给定一个字典,创建 self 类对应的对象
///
/// @param dict 字典
///
/// @return 对象
+ (instancetype)hd_objWithDict:(NSDictionary *)dict; /// 获取类的属性列表数组
///
/// @return 类的属性列表数组
+ (NSArray *)hd_objProperties; @end 在.m中的类方法如下: // 所有字典转模型框架的核心算法
+ (instancetype)hd_objWithDict:(NSDictionary *)dict {
// 实例化对象
id object = [[self alloc] init]; // 使用字典,设置对象信息
// 1> 获得 self 的属性列表
NSArray *proList = [self cz_objProperties]; // 2> 遍历字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { NSLog(@"key %@ --- value %@", key, obj);
// 3> 判断 key 是否在 proList 中
if ([proList containsObject:key]) {
// 说明属性存在,可以使用 `KVC` 设置数值
[object setValue:obj forKey:key];
}
}];
return object;
} const char * kPropertiesListKey = "CZPropertiesListKey"; + (NSArray *)hd_objProperties { // --- 1. 从`关联对象`中获取对象属性,如果有,直接返回!
/**
获取关联对象 - 动态添加的属性
参数:
1. 对象 self
2. 动态属性的 key
返回值
动态添加的`属性值`
*/
NSArray *ptyList = objc_getAssociatedObject(self, kPropertiesListKey);
if (ptyList != nil) {
return ptyList;
} // 调用运行时方法,取得类的属性列表
// Ivar 成员变量
// Method 方法
// Property 属性
// Protocol 协议
/**
参数
1. 要获取的类
2. 类属性的个数指针 返回值
所有属性的`数组`,C 语言中,数组的名字,就是指向第一个元素的地址 retain/create/copy 需要 release,最好 option + click
*/
unsigned int count = 0;
objc_property_t *proList = class_copyPropertyList([self class], &count); NSLog(@"属性的数量 %d", count);
// 创建数组
NSMutableArray *arrayM = [NSMutableArray array]; // 遍历所有的属性
for (unsigned int i = 0; i < count; i++) { // 1. 从数组中取得属性
/**
C 语言的结构体指针,通常不需要 `*`
*/
objc_property_t pty = proList[i]; // 2. 从 pty 中获得属性的名称
const char *cName = property_getName(pty); NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding]; // NSLog(@"%@", name);
// 3. 属性名称添加到数组
[arrayM addObject:name];
} // 释放数组
free(proList); // --- 2. 到此为止,对象的属性数组已经获取完毕,利用关联对象,动态添加属性
/**
参数 1. 对象 self [OC 中 class 也是一个特殊的对象]
2. 动态添加属性的 key,获取值的时候使用
3. 动态添加的属性值
4. 对象的引用关系
*/
objc_setAssociatedObject(self, kPropertiesListKey, arrayM.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return arrayM.copy;
} 注意:必须保证,模型中的属性和字典中的key一一对应。 如果不一致,就会调用[ setValue:forUndefinedKey:],报key找不到的错。
分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖,
就能继续使用KVC,字典转模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{ }
通过运行时字典转模型的好处在于写在NSObject的分类中,和类的关联性不强对类解耦,以后再做字典转模型的时候只需要把这个分类往任何一个程序中一拖,程序中的对象就都具备了这个字典转模型的方法。

5.交叉方法(黑魔法)

开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。方式一:继承系统的类,重写方法.方式二:使用runtime,交换方法.

Runtime在AFN中的使用细节:在AFN的NSURLSessionMangerM方法里面第363行写了一个静态的内联函数,做了一个交叉方法,交叉的是af_resume和resume方法,这样的话,可以在发送网络之前发起一个通知,能接受到任何一个网络请求的事件的变化。

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
// 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步骤二:交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。
UIImage *image = [UIImage imageNamed:@""];
}
@end @implementation UIImage (Image)
// 加载分类到内存的时候调用
+ (void)load
{
// 交换方法 // 获取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:)); // 获取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:)); // 交换方法地址,相当于交换实现方式
method_exchangeImplementations(imageWithName, imageName); } // 既能加载图片又能打印
+ (instancetype)imageWithName:(NSString *)name
{ // 这里调用imageWithName,相当于调用imageName
UIImage *image = [self imageWithName:name]; if (image == nil) {
NSLog(@"加载空的图片");
} return image;
} @end

Runtime简介以及常见的使用场景(此内容非原创,为转载内容)的更多相关文章

  1. MQ(消息队列)常见的应用场景解析

    前言 提高系统性能首先考虑的是数据库的优化,之前一篇文章<数据库的使用你可能忽略了这些>中有提到过开发中,针对数据库需要注意的事项.但是数据库因为历史原因,横向扩展是一件非常复杂的工程,所 ...

  2. iOS开发runtime学习:一:runtime简介与runtime的消息机制

    一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/messag ...

  3. Redis常见的应用场景解析

    Redis是一个key-value存储系统,现在在各种系统中的使用越来越多,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景. Redis特性 一个产品的使用场 ...

  4. 转 fiddler常见的应用场景

    fiddler常见的应用场景   在移动互联网时代,作为软件测试工程师,fiddler绝对是值得掌握并添加进技术栈里的工具之一. 那么,fiddler在日常的测试工作中,一般都有哪些常见的应用场景呢? ...

  5. fiddler常见的应用场景

    在移动互联网时代,作为软件测试工程师,fiddler绝对是值得掌握并添加进技术栈里的工具之一. 那么,fiddler在日常的测试工作中,一般都有哪些常见的应用场景呢? 根据以往工作经验,大概有如下4类 ...

  6. 深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]

    常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复 ...

  7. Runtime常用的几个应用场景

    Runtime常见的几个应用场景. Runtime常见应用场景 具体应用拦截系统自带的方法调用(Method Swizzling黑魔法) 实现给分类增加属性 实现字典的模型和自动转换 JSPatch替 ...

  8. PHP. 01. C/S架构、B/S架构、服务器类型、服务器软件、HTTP协议/服务器、数据库、服务器web开发、PHP简介/常见语法、PHPheader()、 PHP_POST/GET数据获取和错误处理

    C/S架构 Client/Server 指客户端,服务器 架构的意思 优点:性能性高:可将一部分的计算工作放在客户端上,服务器只需处理出局即可   洁面炫酷,可使用更多系统提供的效果 缺点:更新软件需 ...

  9. Redis简介和常见的面试题

    redis介绍及特点 Redis是由意大利人Salvatore Sanfilippo开发的一款内存内存高速缓存数据库. Reids全称为:Remote Dictionary Server(远程数据服务 ...

随机推荐

  1. git 创建分支,删除分支,管理分支

    参考  http://blog.csdn.net/dijason/article/details/9042425   查看分支: 1 查看本地分支: $ git branch 2 查看远程分支 $ g ...

  2. iabtis初探

    1.简介 与Hibernate相比,ibatis属于一种半自动的ORM框架,主要解决了java对象与SQL入参及结果集的映射关系.简单易学.容易上手:但是安全性较差,对于金融等对安全要求较高的系统来说 ...

  3. JPA 系列教程2-单表操作

    JPA Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据.他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibe ...

  4. PHP商品倒计时 php实现当前用户在线人数

    //PHP商品倒计时 date_default_timezone_set("Asia/shanghai");//地区 //配置每天的活动时间段 $starttimestr = &q ...

  5. php des 加密类

    <?php/** *@see Yii CSecurityManager; */class Des{ public static function encrypt($data,$key){ $mo ...

  6. 利用apache组件实现文件上传

    实现文件上传需要引入: commons-fileupload-1.3.2.jar commons-io-2.5.jar commons-logging-1.2.jar <!DOCTYPE htm ...

  7. elasticearch 安装

    1.elasticsearch需要安装jdk1.7以上 2.在ubuntu下不能以root运行,需要建立专门账号 添加组 root@ubuntu:~/Downloads/elasticsearch-/ ...

  8. P图

    照片名称:调出照片柔和的蓝黄色-简单方法,1.打开原图素材,按Ctrl + J把背景图层复制一层,点通道面板,选择蓝色通道,图像 > 应用图像,图层为背景,混合为正片叠底,不透明度50%,反相打 ...

  9. HDU 3487 Play with Chain | Splay

    Play with Chain Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  10. LayoutParams 命名的时候,最好用与子控件相关的字符串命名,

    @Override public View initView() { RelativeLayout container = new RelativeLayout(UIUtils.getContext( ...