iOS runtime 详解和使用场景(最详细的使用教程)
一、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控制器,需要传属性id
、type
,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用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 详解和使用场景(最详细的使用教程)相关推荐
- ios runtime详解
移动开发 Web前端 架构设计 编程语言 互联网 数据库 系统运维 云计算 研发管理 综合 netfoucs.com核心网络 iOS_运行时runtime 最终效果图: 打开XCode帮助文档,搜索O ...
- iOS多线程详解:实践篇
iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI.在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于在iOS中除了主线程,其他子线程是独立 ...
- 李洪强iOS经典面试题156 - Runtime详解(面试必备)
李洪强iOS经典面试题156 - Runtime详解(面试必备) 一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...
- IOS UIView详解
文章目录 IOS UIView详解 1.官方类分析 2. UIView 常用的属性 2.1 UIView的圆角加阴影效果的实现 2.2 UIView 属性 2.2.1 UIView 几何属性 2.2. ...
- FreeEIM 来点新知识iOS UIScrollView详解
老程序员FreeEIM 来点新知识iOS UIScrollView详解 UIScrollView 顾名思义也知道这个是和滚动相关的控件,在Android开发时遇到过ScrollView,当内容的 ...
- iOS绘图详解-多种绘图方式、裁剪、滤镜、移动、CTM
iOS绘图详解 摘要: Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎.它提供了低级别.轻量级.高保真度的2D渲染.该框架可以用于基于路径的 绘 ...
- 阿里云GPU计算型弹性裸金属服务器实例ebmgn6v详解和使用场景
阿里云GPU计算型弹性裸金属服务器实例规格族ebmgn6v详解及适用场景,InstanceTypes分享裸金属ebmgn6v实例规格配置及应用场景: 裸金属ebmgn6v规格特性 基于创新X-Drag ...
- iOS疯狂详解之AFNetworking图片缓存问题
AFNetworking网络库已经提供了很好的图片缓存机制,效率是比较高的,但是我发现没有直接提供清除缓存的功能,可项目通常都需要添加 清除功能的功能,因此,在这里我以UIImageView+AFNe ...
- iOS疯狂详解之开源库
youtube下载神器:https://github.com/rg3/youtube-dl vim插件:https://github.com/Valloric/YouCompleteMe vim插件配 ...
最新文章
- PyTorch 笔记(19)— Tensor 用 GPU 加速
- 力扣(LeetCode)刷题,简单题(第13期)
- Linux下的QQ截图
- qt android 对话框,Qt自定义Dialog
- 0-1背包问题优化算法详解
- OpenGL.Tutorial15_Lightmaps
- linux文本文件和win文本文件的格式互换
- 动手解决jar转txt软件的一个缺陷
- 代码块是什么?该如何使用?
- redis key失效的事件_Redis常见、常用的知识点
- 《30天自制操作系统》03_day_学习笔记
- sharepoint 2007,sharepoint 2010网站的备份还原
- 如何绘制高质量业务流程图
- python写节日祝福小程序_2018新年祝福小程序推荐!
- 【网络安全】目前看到最全的恶意软件分析大合集
- 现代密码学之安全多方计算
- 用c语言求解n阶线性矩阵方程组,求解N阶线性矩阵方程
- 大使、布道师、贡献者,OpenHarmony社区发起三大贡献者激励计划
- EastWave应用案例:机箱屏蔽效能仿真
- clickhouse SLB 服务搭建