一、Runtime介绍

OC是对C语言的扩展,加入了面向对象和消息发送机制,Runtime是OC的一个核心,是用C语言和汇编语言编写。OC是动态运行时语言,在运行时确定一个对象的类型、调用哪个对象的方法,因此需要Runtime来做类和对象的动态创建,消息传递和消息转发等。OC代码最终会转换成Runtime库中对应的函数结构体。任何语言最终都会被编译为汇编语言,再汇编为机器语言。 OC到可执行文件编译过程:

OC->Runtime->C->汇编->可执行文件。

Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码通过 Foundation 框架的NSObject类定义的方法通过对 runtime 函数的直接调用大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。

image.png

二、Runtime源码初探

runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h> 或<objc/message.h>),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。
比如我们创建了一个对象 [[NSObject alloc]init],最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象

image.png

删除掉一些强制转换语句,可以看到调用方法本质就是发消息,[[NSObject alloc]init]语句发了两次消息,第一次发了alloc 消息,第二次发送init 消息。利用这个功能我们可以探究底层,比如block的实现原理。

image.png

三、Runtime功能介绍+使用场景

  • 动态添加属性

  • 动态添加方法

  • 方法交换

  • 归档接档

  • 字典转模型

1.动态添加属性

使用场景: 给系统的类添加属性的时候,可以使用runtime动态添加属性方法;

@implementation NSObject (Property)- (void)setName:(NSString *)name
{/*object:保存到哪个对象中key:用什么属性保存 属性名value:保存值policy:策略,strong,weak*/objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (NSString *)name
{return objc_getAssociatedObject(self, "name");
}
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor orangeColor];//给系统NSObject类动态添加属性nameNSObject *objc = [[NSObject alloc] init];objc.name = @"石虎你是最棒的....";NSLog(@"objc.name = %@",objc.name);}

2.动态添加方法

开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。(例如:会员机制)

  • 添加无参数方法
// 1.创建Person 对象Person *p = [[Person alloc] init];// 2.调用没有实现的eat方法   [p performSelector:@selector(eat)];
// 3.在person.m文件中调用方法:// 作用:调用了一个未实现方法时一定会来到这里
+ (BOOL)resolveInstanceMethod:(SEL)sel
{// 判断方法名是不是eatif (sel == NSSelectorFromString(@"eat")) {// 动态添加eat方法 /*第一个参数:给哪个类添加方法第二个参数:添加什么方法第三个参数IMP:方法实现,函数入口:函数名第四个参数:方法类型 v  没有返回值@ 对象 id:  方法
*/class_addMethod(self, @selector(eat), eat, "v@:");        return YES;}return [super resolveInstanceMethod:sel];
}// 4.eat方法实现// self:方法调用者// _cmd:当前方法编号// 任何一个方法都能调用self,_cmd,其实任何一个方法都有这两个隐式参数
void eat(id self, SEL _cmd)
{NSLog(@"吃东西");
}
  • 添加有参数方法
// 2.调用没有实现的run方法   [p performSelector:@selector(run:) withObject:@10];
// 3.在person.m文件中调用方法:// 作用:调用了一个未实现方法时一定会来到这里
+ (BOOL)resolveInstanceMethod:(SEL)sel
{// 判断方法名是不是eatif (sel == NSSelectorFromString(@"run:")) {// 动态添加run方法 /*第一个参数:给哪个类添加方法第二个参数:添加什么方法第三个参数IMP:方法实现,函数入口:函数名第四个参数:方法类型 v  没有返回值@ 对象 id:  方法
*/class_addMethod(self, @selector(run:), run, "v@:@");        return YES;}return [super resolveInstanceMethod:sel];
}// 4.run方法实现// self:方法调用者// _cmd:当前方法编号// 任何一个方法都能调用self,_cmd,其实任何一个方法都有这两个隐式参数
void run(id self, SEL _cmd,  NSNumber *metre)
{NSLog(@"跑了%@米",metre);
}

3.方法交换(Swizzle 黑魔法)

平时我们app中用到的系统方法有很多,有时候我们需要对系统方法进行修改,已实现我们的需求和解决问题,我们不可能每个去改去处理.所以我们就要用到方法替换了.

使用场景: array越界空等引起的崩溃, button重复点击, image空图片懒加载等很多功能.

Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
Method My_imageNameMethod = class_getClassMethod(self, @selector(My_imageNamed:));
method_exchangeImplementations(imageNameMethod, My_imageNameMethod);// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.+ (instancetype)My_imageNamed:(NSString *)name
{// 这里调用My_imageNamed,相当于调用imageNamedUIImage *image = [self My_imageNamed:name];if (image == nil) {NSLog(@"加载空的图片");}return image;
}

4.归档解档

使用场景: 归档解档
不用运行时的归档方法:(还好只有5个属性,如果20个,30个或者后台突然增加了属性,这么直接写死估计代码就不灵了)

//  YYPerson.m#import "YYPerson.h"@implementation YYPerson// 当将一个自定义对象保存到文件的时候就会调用该方法
// 在该方法中说明如何存储自定义对象的属性
// 也就说在该方法中说清楚存储自定义对象的哪些属性
- (void)encodeWithCoder:(NSCoder *)aCoder
{NSLog(@"调用了encodeWithCoder:方法");[aCoder encodeObject:self.name forKey:@"name"];[aCoder encodeInteger:self.age forKey:@"age"];[aCoder encodeDouble:self.height forKey:@"height"];
}// 当从文件中读取一个对象的时候就会调用该方法
// 在该方法中说明如何读取保存在文件中的对象
// 也就是说在该方法中说清楚怎么读取文件中的对象
- (id)initWithCoder:(NSCoder *)aDecoder
{NSLog(@"调用了initWithCoder:方法");//注意:在构造方法中需要先初始化父类的方法if (self=[super init]) {self.name=[aDecoder decodeObjectForKey:@"name"];self.age=[aDecoder decodeIntegerForKey:@"age"];self.height=[aDecoder decodeDoubleForKey:@"height"];}return self;
}
@end

runtime 归档接档

//
//  Apply.m
//  01-RuntimeSendMessage
//
//  Created by Mac on 2019/11/1.
//  Copyright © 2019 Mac. All rights reserved.
//#import "Apply.h"
#import <objc/runtime.h>@implementation Apply
// 归档的时候,系统会使用编码器把当前对象编码成二进制流
- (void)encodeWithCoder:(NSCoder *)coder {unsigned int count = 0;// 获取所有实例变量Ivar *ivars = class_copyIvarList([self class], &count);// 遍历for (int i = 0; i < count; i++) {Ivar ivar = ivars[I];const char *name = ivar_getName(ivar);NSString *key = [NSString stringWithUTF8String:name];// KVCid value = [self valueForKey:key];// 编码[coder encodeObject:value forKey:key];}// 因为是 C 语言的东西,不会自动释放,所以这里需要手动释放。free(ivars);
}// 解档的时候,系统会把二进制流解码成对象
- (instancetype)initWithCoder:(NSCoder *)coder {self = [super init];if (self) {unsigned int count = 0;// 获取所有实例变量Ivar *ivars = class_copyIvarList([self class], &count);// 遍历for (int i = 0; i < count; i++) {Ivar ivar = ivars[I];const char *name = ivar_getName(ivar);NSString *key = [NSString stringWithUTF8String:name];id value = [coder decodeObjectOfClasses:[NSSet setWithObject:[self class]] forKey:key];// KVC[self setValue:value forKey:key];}free(ivars);}return self;
}+ (BOOL)supportsSecureCoding {return YES;
}@end
  • 在使用的时候
// 4.自动解归档Apply *apply = [Apply new];apply.name = @"张三";apply.age = @18;apply.nick = @"zhangsan";Apply *apply_2;NSString *fileName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"archive.plist"];if (@available(iOS 11.0, *)) {NSData *data_1 = [NSKeyedArchiver archivedDataWithRootObject:apply requiringSecureCoding:YES error:nil];[data_1 writeToFile:fileName atomically:YES];NSData *data_2 = [[NSData alloc] initWithContentsOfFile:fileName];apply_2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[Apply class] fromData:data_2 error:nil];} else {[NSKeyedArchiver archiveRootObject:apply toFile:fileName];apply_2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName];}NSLog(@"name: %@, age: %@, nick: %@", apply_2.name, apply_2.age, apply_2.nick);

查看原文链接

5.字典转模型

使用场景:字典转模型时,希望可以不用与字典中属性一一对应(案例:NSObject+JSONExtension.h)
方法:可以使用runtime,遍历模型中有多少个属性,直接去字典中取出对应value,给模型赋值

+ (instancetype)modelWithDict:(NSDictionary *)dict
{id objc = [[self alloc] init];int count = 0;// 成员变量数组 指向数组第0个元素Ivar *ivarList = class_copyIvarList(self, &count);// 遍历所有成员变量for (int i = 0; i < count; i++) {// 获取成员变量 userIvar ivar = ivarList[i];// 获取成员变量名称NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];// 获取成员变量类型NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];//  @"@\"User\"" -> @"User"type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];// 成员变量名称转换keyNSString *key = [ivarName substringFromIndex:1];// 从字典中取出对应value dict[@"user"] -> 字典id value = dict[key];// 二级转换// 并且是自定义类型,才需要转换if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典才需要转换Class className = NSClassFromString(type);// 字典转模型value = [className modelWithDict:value];}// 给模型中属性赋值 key:user value:字典 -> 模型if (value) {[objc setValue:value forKey:key];}}return objc;
}

6.万能界面跳转方法

使用场景: 消息接收后跳转

利用runtime动态生成对象、属性、方法这特性,我们可以先跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性idtype,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了 ---O(∩_∩)O哈哈哈

// 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
NSDictionary *userInfo = @{@"class": @"HSFeedsViewController",@"property": @{@"ID": @"123",@"type": @"12"}};
  • 跳转界面
- (void)push:(NSDictionary *)params
{// 类名NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];// 从一个字串返回一个类Class newClass = objc_getClass(className);if (!newClass){// 创建一个类Class superClass = [NSObject class];newClass = objc_allocateClassPair(superClass, className, 0);// 注册你创建的这个类objc_registerClassPair(newClass);}// 创建对象id instance = [[newClass alloc] init];// 对该对象赋值属性NSDictionary * propertys = params[@"property"];[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {// 检测这个对象是否存在该属性if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {// 利用kvc赋值[instance setValue:obj forKey:key];}}];// 获取导航控制器UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];// 跳转到对应的控制器[pushClassStance pushViewController:instance animated:YES];
}
  • 检测对象是否存在该属性
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{unsigned int outCount, i;// 获取对象里的属性列表objc_property_t * properties = class_copyPropertyList([instanceclass], &outCount);for (i = 0; i < outCount; i++) {objc_property_t property =properties[i];//  属性名转成字符串NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];// 判断该属性是否存在if ([propertyName isEqualToString:verifyPropertyName]) {free(properties);return YES;}}free(properties);return NO;
}

具体使用和代码: https://github.com/HHuiHao/Universal-Jump-ViewController

  • 作者开发经验总结的文章推荐,持续更新学习心得笔记
    Runtime 10种用法(没有比这更全的了)
    成为iOS顶尖高手,你必须来这里(这里有最好的开源项目和文章)
    iOS逆向Reveal查看任意app 的界面
    JSPatch (实时修复App Store bug)学习(一)
    iOS 高级工程师是怎么进阶的(补充版20+点)
    扩大按钮(UIButton)点击范围(随意方向扩展哦)
    最简单的免证书真机调试(原创)
    通过分析微信app,学学如何使用@2x,@3x图片
    TableView之MVVM与MVC之对比
    使用MVVM减少控制器代码实战(减少56%)
    ReactiveCocoa添加cocoapods 配置图文教程及

iOS runtime 详解和使用场景(最详细的使用教程)相关推荐

  1. ios runtime详解

    移动开发 Web前端 架构设计 编程语言 互联网 数据库 系统运维 云计算 研发管理 综合 netfoucs.com核心网络 iOS_运行时runtime 最终效果图: 打开XCode帮助文档,搜索O ...

  2. iOS多线程详解:实践篇

    iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI.在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于在iOS中除了主线程,其他子线程是独立 ...

  3. 李洪强iOS经典面试题156 - Runtime详解(面试必备)

    李洪强iOS经典面试题156 - Runtime详解(面试必备)   一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...

  4. IOS UIView详解

    文章目录 IOS UIView详解 1.官方类分析 2. UIView 常用的属性 2.1 UIView的圆角加阴影效果的实现 2.2 UIView 属性 2.2.1 UIView 几何属性 2.2. ...

  5. FreeEIM 来点新知识iOS UIScrollView详解

     老程序员FreeEIM 来点新知识iOS UIScrollView详解 UIScrollView 顾名思义也知道这个是和滚动相关的控件,在Android开发时遇到过ScrollView,当内容的 ...

  6. iOS绘图详解-多种绘图方式、裁剪、滤镜、移动、CTM

    iOS绘图详解 摘要: Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎.它提供了低级别.轻量级.高保真度的2D渲染.该框架可以用于基于路径的 绘 ...

  7. 阿里云GPU计算型弹性裸金属服务器实例ebmgn6v详解和使用场景

    阿里云GPU计算型弹性裸金属服务器实例规格族ebmgn6v详解及适用场景,InstanceTypes分享裸金属ebmgn6v实例规格配置及应用场景: 裸金属ebmgn6v规格特性 基于创新X-Drag ...

  8. iOS疯狂详解之AFNetworking图片缓存问题

    AFNetworking网络库已经提供了很好的图片缓存机制,效率是比较高的,但是我发现没有直接提供清除缓存的功能,可项目通常都需要添加 清除功能的功能,因此,在这里我以UIImageView+AFNe ...

  9. iOS疯狂详解之开源库

    youtube下载神器:https://github.com/rg3/youtube-dl vim插件:https://github.com/Valloric/YouCompleteMe vim插件配 ...

最新文章

  1. PyTorch 笔记(19)— Tensor 用 GPU 加速
  2. 力扣(LeetCode)刷题,简单题(第13期)
  3. Linux下的QQ截图
  4. qt android 对话框,Qt自定义Dialog
  5. 0-1背包问题优化算法详解
  6. OpenGL.Tutorial15_Lightmaps
  7. linux文本文件和win文本文件的格式互换
  8. 动手解决jar转txt软件的一个缺陷
  9. 代码块是什么?该如何使用?
  10. redis key失效的事件_Redis常见、常用的知识点
  11. 《30天自制操作系统》03_day_学习笔记
  12. sharepoint 2007,sharepoint 2010网站的备份还原
  13. 如何绘制高质量业务流程图
  14. python写节日祝福小程序_2018新年祝福小程序推荐!
  15. 【网络安全】目前看到最全的恶意软件分析大合集
  16. 现代密码学之安全多方计算
  17. 用c语言求解n阶线性矩阵方程组,求解N阶线性矩阵方程
  18. 大使、布道师、贡献者,OpenHarmony社区发起三大贡献者激励计划
  19. EastWave应用案例:机箱屏蔽效能仿真
  20. clickhouse SLB 服务搭建

热门文章

  1. Java开发的10位牛人
  2. 判断二分图【bfs】
  3. USB-C接口iPhone再出续集,小哥改良换接口工艺增加防水功能,还打算筹钱量产...
  4. 2009年经典到变态的语录100句
  5. 线上教育相较于传统线下教育的优点
  6. 如何在Safari Mac中启用暗黑模式?
  7. UML类图--依赖关系
  8. Causal Intervention for Leveraging Popularity Bias in Recommendation论文笔记
  9. 网络视频直播系统,搭建直播服务器环境
  10. 【180616】VC++开发的麻将游戏