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

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,只有对象才能发送消息

举个简单的例子:如下

(1).调用对象方法:
// 创建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, "[email protected]:");

    }

    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:@"123"];
}
@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
时间: 2024-08-11 05:24:37

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

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

一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/message.h> #import "Person.h" /* 总结: 1: runtime:必须要导入头文件 <objc/message.h>,此头文件中已经引入了<objc/runtime.h> 任何方法调用本质:发送一个消息,用runtime发送消息.OC底层实现

runtime简介

一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用. 事实证明: 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错. 在编译阶段,C语言调用未实现的函数就会报错. 二.runtime作用 1.发送消息 方法调用

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

前言 j提高系统性能首先考虑的是数据库的优化,之前一篇文章<数据库的使用你可能忽略了这些>中有提到过开发中,针对数据库需要注意的事项.但是数据库因为历史原因,横向扩展是一件非常复杂的工程,所有我们一般会尽量把流量都挡在数据库之前. 不管是无限的横向扩展服务器,还是纵向阻隔到达数据库的流量,都是这个思路.阻隔直达数据库的流量,缓存组件和消息组件是两大杀器. MQ简介 MQ,Message queue,消息队列,就是指保存消息的一个容器.具体的定义这里就不类似于数据库.缓存等,用来保存数据的.当然

iOS中常见的 Crash 场景以及解决方法

1. 常见的 Crash 场景 访问了僵尸对象 访问了不存在的方法 数组越界 在定时器下一次回调前将定时器释放,会Crash 2.  关于BAD_ACCESS  出现的原因: 访问了野指针, 比如访问已经释放对象的成员变量或者发消息, 死循环等; 解决方法: 1.  重写对象的respondsToSelector 方法, 先找到出现 EXECBADACCESS 前访问的最后一个 object; 2. 设置Enable Zombie Objects; 3. 设置全局断点快速定位问题代码所在行,接收

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

常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复用HTTP连接,降低资源申请的开销.通常我们认为HTTP请求时非常昂贵的,并且也是比较耗费资源和性能的,所以线程池在这里就扮演了非常重要的角色. 在线程池的章节中非常详细的讨论了线程池的原理和使用,同时也提到了,线程池的配置和参数对性能的影响是巨大的.不尽如此,受限于资源(机器的性能.网络的带宽等等

NFS服务的简介及常见故障解决方法

NFS服务的简介及常见故障解决方法 1.NFS基本介绍 (1)NFS简介 NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布.功能是让客户端通过网络访问不同主机上磁盘里的数据,主要用在类Unix系统上实现文件共享的一种方法.NFS在文件传送或信息传送过程中依赖于RPC协议. (2)NFS服务需要安装的软件 nfs-utils-* :包括基本的NFS命令与监控程序 rpcbind-* :支持安全NFS RP

转 fiddler常见的应用场景

在移动互联网时代,作为软件测试工程师,fiddler绝对是值得掌握并添加进技术栈里的工具之一. 那么,fiddler在日常的测试工作中,一般都有哪些常见的应用场景呢? 根据以往工作经验,大概有如下4类应用场景: 辅助定位bug: 构建模拟测试场景:  APP弱网模拟测试:  前端性能分析及优化: 1.辅助定位bug 合格的软件测试工程师,不仅仅需要能够发现bug,还需要能透过bug表象,分析出问题根本原因,从而提升bug的解决效率,突显bige. 通过fiddler可以抓取request和res

常见数据结构应用场景

通用数据结构 可以简单的按照速度将通用数据结构划分为:数组和链表(最慢),树(较快),哈希表(最快).增.删.改.查是四大常见操作,不过其实可以浓缩为两个操作:增和查.删除操作和和修改操作都是建立在查找操作上的,所以完美的数据结构应该是具有较高的插入效率和查找效率. 通用数据结构关系 可以根据下图选择合适的通用数据结构: 数组 使用场景 数组在以下三个情形下很有用: 1)数据量较小. 2)数据规模已知. 3)随机访问,修改元素值. 如果插入速度很重要,选择无序数组.如果查找速度很重要,选择有序数

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

C/S架构 Client/Server 指客户端,服务器 架构的意思 优点:性能性高:可将一部分的计算工作放在客户端上,服务器只需处理出局即可   洁面炫酷,可使用更多系统提供的效果 缺点:更新软件需版本同步 不同设备访问:必须安装了客户端才能登陆 B/S架构 Browser /Server 指浏览器, 服务器.是WEB兴起后的一种结构 优点:更新简洁,对用户来说只需刷新浏览器即可   多设备同步:只要能够使用浏览器即可登录 缺点:性能较低:当时随着硬件性能的提升,这个差距在缩小   浏览器兼容