IOS Object-C 中Runtime详解

最近了解了一下OC的Runtime,真的是OC中很强大的一个机制,看起来比较底层,但其实可以有很多活用的方式。
什么是Runtime
我们虽然是用Objective-C写的代码,其实在运行过程中都会被转化成C代码去执行。比如说OC的方法调用都会转成C函数 id objc_msgSend ( id self, SEL op, … ); 而OC中的对象其实在Runtime中都会用结构体来表示,这个结构体中包含了类名、成员变量列表、方法列表、协议列表、缓存等。
类在Runtime中的表示:
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
整个Runtime机制其实可以挖的点很多,这里只是简单的介绍一些常见的用法,如果将其细细解析,相信一定会对OC的理解加深几个层面。
获取属性/方法/协议列表
最直接的一种用法,就是获取我们的结构体中存储的对象的属性、方法、协议等列表,从而获取其所有这些信息。
要获取也比较简单,但是自己尝试之前需要注意几点:
一定要自己给类加几个属性、方法,遵循一些协议,否则当然是看不到输出信息的。
要使用这些获取的方法,需要导入头文件 #import
#import <objc/runtime.h>
// 输出类的一些信息
- (void)logInfo {
unsigned int count;// 用于记录列表内的数量,进行循环输出
// 获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property --> %@", [NSString stringWithUTF8String:propertyName]);
}
// 获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i < count; i++) {
Method method = methodList[i];
NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
}
// 获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar --> %@", [NSString stringWithUTF8String:ivarName]);
}
// 获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i < count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol --> %@", [NSString stringWithUTF8String:protocolName]);
}
}
方法调用的过程
调用方法分为调用实例方法和调用类方法,在结构体我们可以看到第一个就是一个 isa 指针,会指向类对象或者元类,什么叫元类呢?
每个实例对象有个isa的指针,他指向对象的类,而类里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。
通过isa,就可以不断往上方去回溯自己的父类等,而方法的调用也利用了这个过程:
以上就是方法调用的过程。我们可以看到的是,所谓重写父类方法,并不是抹除了父类方法,父类的方法还是存在的,只是我们在子类里面找到了就不会再去父类里找了,是这个层面的“覆盖”。而我们熟悉的 super 调用父类的方法实现,就是告知要去调用父类实现的标识。
拦截调用与动态添加
上面说到了拦截调用,就是在所有地方都找不到方法的实现的话,会出发拦截调用,可以自己去做一些处理避免程序崩溃。那在什么地方做处理呢?NSObject有四个方法可以用来做处理:
// 调用不存在的类方法时触发,默认返回NO,可以加上自己的处理后返回YES + (BOOL)resolveClassMethod:(SEL)sel; // 调用不存在的实例方法时触发,默认返回NO,可以加上自己的处理后返回YES + (BOOL)resolveInstanceMethod:(SEL)sel; // 将调用的不存在的方法重定向到一个其他声明了这个方法的类里去,返回那个类的target - (id)forwardingTargetForSelector:(SEL)aSelector; // 将调用的不存在的方法打包成 NSInvocation 给你,自己处理后调用 invokeWithTarget: 方法让某个类来触发 - (void)forwardInvocation:(NSInvocation *)anInvocation;
假设我们成功拦截下来了,那我们可以做什么处理呢?这个其实就是根据具体情况看你要怎么处理了。但这里有一个久闻其名的东西就可以派上用场了:动态添加方法。
我们知道OC是动态的,也就是说很多东西它不是在编译时去判断,而是在运行时去处理的,这其实带来了很大的灵活性,比如这里我们对于一些不存在的方法的调用,就可以动态去添加上一个方法来代替我们要调用的方法。
比如我们添加一个Button,点击事件我们关联到一个没有具体实现的实例方法,这种情况下如果不做任何处理那么点击Button后一定会崩溃,但是如果我们拦截并动态添加一个方法来报警,就不会崩溃了:
@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;
- (void)notHas;// 要调用的实例方法,没有具体实现
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 添加按钮
self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
[self.logBtn setTitle:@"测 试" forState:UIControlStateNormal];
[self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
// 添加没有实现的点击事件
[self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logBtn];
}
// 拦截对不存在的方法的调用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"notFind!");
// 给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
// 注意要返回YES
return YES;
}
// 要动态添加的方法,这是一个C方法
void runAddMethod(id self, SEL _cmd, NSString *string) {
NSLog(@"动态添加一个方法来提示");
}
按照上面的处理,点击按钮后就不会崩溃,而是转到了对 runAddMethod 方法的调用。其实更明确地说,应该是重现了对要调用的方法的实现,将原本要调用的方法的实现,改为了一个新的实现。class_addMethod 方法的第二个参数是要重写的方法,这里用的就是传进来的参数sel,第三个参数就是重写后的实现。第四个参数是方法的签名。
关联对象
什么叫关联对象?说通俗一点,我们都知道用Category类别可以给一些已经存在的,比如系统的类添加方法,但是不能添加新属性,那怎么添加属性呢?一种直接的方法是继承,但如果只是为了添加一个属性就去做一次继承,还是有点重,这时候,就可以用关联对象的方法。
有两个相关的方法:
objc_setAssociatedObject 方法用来给类关联一个属性;
objc_getAssociatedObject 方法用来获取之前关联的属性。
比如说给自己这个类关联一个字符串:
// 关联对象 static char associatedObjectKey; objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要关联的字符串对象内容", OBJC_ASSOCIATION_RETAIN_NONATOMIC); NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey); NSLog(@"关联对象:%@", theString);
我们先给self关联了一个字符串内容,然后通过get方法获取了关联的字符串内容,并输出。
从代码中其实也可以猜到各个参数的意思,self的参数就是要处理的类;associatedObjectKey 的参数其实就类似于key,用来标识区分你要关联的这个对象;第三个参数是要关联的对象;第四个参数是关联的策略,用命名就可以看出来全是在添加@property属性时用到的一些修饰符,有五种策略:
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
熟悉@property属性修饰符的应该能直接明白了,不熟悉的可以看这篇文章:传送门:iOS中assign、retain、copy、weak、strong的区别以及nonatomic的含义
当然,你也可以和类别一起用,创建两个方法用来关联和获取对象,比如下面这样:
//添加关联对象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//获取关联对象
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}
这样就既能通过Category类别来添加方法,用一起顺便提供了对属性的添加了。
结
以上是对Runtime的一点浅薄的理解和使用,Runtime的天地应该是很广阔的,也能挖出很多高级的使用方法来,对于理解OC的运行机制是很有帮助的。
源码下载:http://xiazai./201703/yuanma/RuntimeDemo-master().rar
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# OC中Runtime
# IOS
# Object-C
# 中Runtime详解
# 总结iOS中runtime的使用
# iOS runtime动态添加方法示例详解
# iOS Runtime详解(新手也看得懂)
# iOS runtime forwardInvocation详解及整理
# iOS开发之Objective-c的Runtime理解指南
# 的是
# 不存在
# 自己的
# 是在
# 我们可以
# 就可以
# 重写
# 找到了
# 方法来
# 几个
# 就会
# 还没
# 有个
# 可以用
# 去做
# 如果没有
# 什么叫
# 第三个
# 应该是
# 是一个
相关文章:
宝塔面板创建网站无法访问?如何快速排查修复?
C++如何编写函数模板?(泛型编程入门)
,sp开头的版面叫什么?
建站之星×万网:智能建站系统+自助建站平台一键生成
北京的网站制作公司有哪些,哪个视频网站最好?
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
高防服务器租用首荐平台,企业级优惠套餐快速部署
如何在七牛云存储上搭建网站并设置自定义域名?
Swift中循环语句中的转移语句 break 和 continue
公司网站制作价格怎么算,公司办个官网需要多少钱?
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
建站之星多图banner生成与模板自定义指南
常州自助建站:操作简便模板丰富,企业个人快速搭建网站
小说建站VPS选用指南:性能对比、配置优化与建站方案解析
建站之星各版本价格是多少?
如何用PHP快速搭建高效网站?分步指南
制作公司内部网站有哪些,内网如何建网站?
C#怎么使用委托和事件 C# delegate与event编程方法
网站制作企业,网站的banner和导航栏是指什么?
css网站制作参考文献有哪些,易聊怎么注册?
个人摄影网站制作流程,摄影爱好者都去什么网站?
网站制作新手教程,新手建设一个网站需要注意些什么?
如何通过商城免费建站系统源码自定义网站主题?
Python路径拼接规范_跨平台处理说明【指导】
如何通过山东自助建站平台快速注册域名?
香港服务器WordPress建站指南:SEO优化与高效部署策略
建站之星收费标准详解:套餐费用及年费价格表一览
较简单的网站制作软件有哪些,手机版网页制作用什么软件?
,制作一个手机app网站要多少钱?
香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南
大同网页,大同瑞慈医院官网?
魔毅自助建站系统:模板定制与SEO优化一键生成指南
微信小程序制作网站有哪些,微信小程序需要做网站吗?
微信网站制作公司有哪些,民生银行办理公司开户怎么在微信网页上查询进度?
如何零基础在云服务器搭建WordPress站点?
建站DNS解析失败?如何正确配置域名服务器?
如何选择PHP开源工具快速搭建网站?
如何在云服务器上快速搭建个人网站?
c# Task.Yield 的作用是什么 它和Task.Delay(1)有区别吗
学校免费自助建站系统:智能生成+拖拽设计+多端适配
如何快速搭建高效WAP手机网站吸引移动用户?
建站之星如何优化SEO以实现高效排名?
公司网站的制作公司,企业网站制作基本流程有哪些?
如何选择美橙互联多站合一建站方案?
做企业网站制作流程,企业网站制作基本流程有哪些?
建站之星如何快速解决建站难题?
建站三合一如何选?哪家性价比更高?
网站代码制作软件有哪些,如何生成自己网站的代码?
威客平台建站流程解析:高效搭建教程与设计优化方案
想学网站制作怎么学,建立一个网站要花费多少?
*请认真填写需求信息,我们会在24小时内与您取得联系。