runtime--小白看过来
目录
- RunTime 概述
- RunTime消息机制
- RunTime交换方法
- RunTime消息转发
- RunTime关联对象
- RunTime实现字典与模型互转
1.RunTime 概述
我们在面试的时候,经常都会被问到这么个问题:为什么说OC是一门动态的语言???其实也就是想知道你对runtime的了解程度。
2.RunTime消息机制
1.消息机制是运行时里面最重要的机制,OC中任何方法的调用,本质都是发送消息。eg:
当我们实例化这个对象时:MyClass *object = [[MyClass alloc] init]; 就会调这个实例化方法:[object showUserName];
我们大概来看一下它的底层实现:
证明过程:
在终端用命令打开此类文件所在的文件夹,继续写入命令:clang -rewrite-objc MyClass.m(把oc代码转写成
c/c++代码,我们常用它来窥探OC的底层实现),不一会在原来的同一目录下会多出一个 MyClass.cpp 文件
双击打开,可以看到 init 方法已经被编译器转化为下面这样:

objc_msgSend 函数被定义在 objc/message.h 目录下,其函数原型是:
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
该函数有两个参数,一个 id 类型(消息接收对象),一个 SEL 类型(方法的selector)。
@selector (SEL):是一个SEL方法选择器。SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。
对于一个类中,每一个方法对应着一个SEL。所以一个类中不能存在2个名称相同的方法(有歧义。。。),即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。
歧义解释:- (void)go {} + (void)go {} 这两个方法可以共存(我们知道,这两个方法的名字都是go)。
我个人的理解是:当我们向一个对象或一个类发送消息时,runtime都会根据方法名去这个对象所属的这个类的方法列表中查找方法,而方法列表的外层应该是一个字典,根据所传的接收消息对象不同,查找的方法列表也不同。
objc_msgSend([MyClass class], @selector(go));
objc_msgSend([[MyClass alloc] init], @selector(go));
Id :是一个结构体指针类型,它可以指向 Objective-C 中的任何对象。
Class vclass = NSClassFromString(@"ViewController");
id vc = [[vclass alloc] init];
objc_object 结构体定义如下:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};
这就是我们通常所说的对象,这个结构体只有一个成员变量 isa,对象可以通过 isa 指针找到其所属的类。isa 是一个 Class 类型的成员变量,那么 Class 又是什么呢?如下:

经过以上的讲述,我们大概可以了解到,当调用一个方法时,其运行过程大致如下:

3.RunTime交换方法
应用场景:当系统自带的方法功能不够,需要给系统自带的方法扩展一些功能时。
eg:实现image添加图片的时候,自动判断image是否为空,如果为空则提醒图片不存在。
有以下三种比较好的解决方法:
1.自定义类, 重写系统自带的imageName:方法,这种方法虽然可以实现,但是它的弊端就是必须要使用自己的类,依赖性强。
2.给UIImage添加一个分类, 改变系统类的实现,给系统的类添加方法的时候调用(每次使用都需要导入头文件,并且如果项目比较大,之前使用的方法全部需要更改)。
3.使用runtime的交互方法,给系统的方法添加功能. 具体实现 : 添加一个分类 --> 在分类中提供一个自定义判空增加新功能的方法 --> 将这个方法的实现和系统自带的方法的实现交互.
交换方法的本质其实是交换两个方法的实现,即调换le_imageNamed:和imageName:方法,达到调用le_imageNamed:其实就是调用imageNamed:方法的目的。
那么首先需要明白方法在哪里交换,因为以后都是使用自己定义的方法取代系统的方法,所以,当程序一启动,就要求能使用自己定义的功能方法。我们一般在 + (void)load 方法里实现交换方法 (当程序启动的时候就会调用该方法,换句话说,只要程序一启动就会调用load方法,整个程序运行中只会调用一次)

4.RunTime消息转发
在方法调用的时候,如果没有找到方法就会转向消息转发(拦截调用)。
拦截调用是指,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的五个方法来处理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
- (id)forwardingTargetForSelector:(SEL)aSelector;
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
- 第二个方法和第一个方法相似,只不过处理的是实例方法。
- 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
- 第四个方法是获取方法签名进入下一步,进行消息转发
- 第五个方法将你调用的不存在的方法打包成
NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
eg: Monkey *monkey = [[Monkey alloc] init];
((void (*) (id, SEL)) objc_msgSend) (monkey, sel_registerName("fly")); (猴子是不可能有飞的天赋的,除非它是孙猴子。。。)


"v@:"的含义请看官方文档:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
消息转发的作用主要是处理异常,下面来看一个实例:
NSMutableArray *arrM = [NSMutableArray array];
NSString *str = @"”;
[arrM addObject:str]; 这行代码会导致程序崩溃,因为数组添加的对象不能为空。解决方法:

总结:可以利用category + runtime + 异常的捕获写一个防止崩溃的框架,已经有大神在做了,具体请看:https://github.com/chenfanfang/AvoidCrash
5.RunTime关联对象
应用场景:当你准备用一个系统的类或者是你写的类,但是这个类并不能满足你的需求,你需要额外添加一个属性。
一般解决办法要么是extends(继承),要么使用category(类别)。
但基本不推荐使用extends,主要是耦合性太强,主要使用category。
我们都知道,分类中是无法设置属性的,如果在分类的声明中写@property 只能为其生成get 和
set 方法的声明,
这时候,runtime的关联属性就能发挥它的作用了。
注意:使用property的时候,同时重写set get方法会报错。复写了get和set方法之后@property默认生成的@synthesize就不会起作用了,这也就意味着你的类不会自动生成出来实例变量了,你就必须要自己声明实例变量。
设置关联对象使用
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
key 即可取出对应的被关联对象
获取关联对象使用
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
要删除某一个被关联的对象,使用
objc_setAssociatedObject 方法将对应的
key 设置成
nil 即可。
移除源对象中所有的关联对象
OBJC_EXPORT
void objc_removeAssociatedObjects(id object)
eg:为view类添加一个点击手势(主要实现它的子类(UILabel 、UIImageView可以像button一样,有自己的点击方法)

6.RunTime实现字典与模型互转

其中,获取属性的方法可以让我们拿到各个类的私有属性,让后利用kvc赋值,加以应用。
归档解档:
常规的归档解档方法,可是当model的属性很多时,这样写就有点尴尬了。。。

利用runtime实现批量归档解档:

我们在使用一些json转model的第三方框架(JSONModel,MJExtension等)时,它们的底层都是利用runtime去实现的:
字典转模型的时候:
1.根据字典的 key 生成 setter 方法
2.使用 objc_msgSend 调用 setter 方法为 Model 的属性赋值(或者 KVC)

模型转字典的时候:
1.调用 class_copyPropertyList 方法获取当前 Model 的所有属性
2.调用 property_getName 获取属性名称
3.根据属性名称生成 getter 方法
4.使用 objc_msgSend 调用 getter 方法获取属性值(或者 KVC)

感谢这几个篇文章对我的帮助:
http://www.cocoachina.com/ios/20160523/16386.html
http://www.jianshu.com/p/5d625f86bd02
由于自己之前查阅资料时,没有及时写总结,现在有点闲时了才开始写,所以有些参考文章都不记得了,请相关作者见谅。。。
runtime--小白看过来的更多相关文章
- 小白看过来runtinme
RunTime 概述 RunTime消息机制 RunTime交换方法 RunTime消息转发 RunTime关联对象 RunTime实现字典与模型互转 1.RunTime 概述 我们在面试的时候,经常 ...
- 写给小白看的入门级 Java 基本语法,强烈推荐
之前写的一篇我去阅读量非常不错,但有一句留言深深地刺痛了我: 培训班学习半年,工作半年,我现在都看不懂你这篇文章,甚至看不下去,对于我来说有点深. 从表面上看,这句话有点讽刺我的文章写得不够通俗易懂的 ...
- 写给小白看的Mysql事务
1 为什么需要事务 在网上的很多资料里,其实没有很好的解释为什么我们需要事务.其实我们去学习一个东西之前,还是应该了解清楚这个东西为什么有用,硬生生的去记住事务的ACID特性.各种隔离级别个人认为没有 ...
- 写给小白看的 JavaScript 异步
某天突然写了个方法要从后台调用数据,显示在前台页面,但是输出结果总是空 undefined,得不到数据.多方找资料才发现,原来是入了 JS 异步的 “坑”. 我们常常听到单线程.多线程.同步.异步这些 ...
- 给小白看的KMP算法
浅谈KMP算法: (大部分人的KMP写法都是不一样的) 一: 先给大家推荐一个讲kmp特别好理解的一个博客:阮一峰 二: 再给大家介绍一点相关概念: 栗子: P串: ABCBD 前缀:A,AB,AB ...
- 353 stars Java项目!Java小白必看!austin介绍 【第一话】
有好几个群友问我为什么最近更新变慢了.工作忙是一方面,另一方面是我更新文章的动力确实下降了.近大半年一直在更新的<对线面试官>系列,到现在已经40篇了. 说实话,当时我更新该系列有很大一部 ...
- runtime使用小例子 - 给对象O-C属性赋值
这些日子在家里学习runtime,看runtime的一些方法和前辈们的博客,我也尝试着写几个runtime有效的运用 一.给对象属性赋值,例如一个WebEntity类 她有三个属性:NSString. ...
- ios之runtime学习
今天学习了一下ios的runtime,看了其他博主的博客写的很不错,自己就不班门弄斧了,仅在此转载: 1.关于oc中类和元类:http://husbandman.diandian.com/post/2 ...
- 偏前端--之小白学习本地存储与cookie
百度了很多都是讲的理论,什么小于4kb啊之类的,小白看了一脸懵逼复制到html中为什么没效果!!哈哈.我来写一个方便小白学习. 贴图带文字描述,让小白也运行起来,然后自己再去理解... 1. cook ...
- ComicEnhancerPro 系列教程二十:用“文件比较”看有损、无损
作者:马健邮箱:stronghorse_mj@hotmail.com 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23 教程二十:用“文件比较” ...
随机推荐
- shell二位数组——终端字符下降动画
猜想:Shell支持关联数组,可以利用关联数组模拟二维数组. [验证猜想] #!/bin/bash array[1,1]=1 array[2,1]=2 array[3,1]=3 for i in `s ...
- Linux下利用expect,不用交互模式,直接登陆远程主机
Linux环境下只有在机器20.200.254.18上ssh dataconv@20.200.31.23才能连接到23的机器,而且还需要输入密码(每次都需要输入地址,密码很烦),所以利用expect写 ...
- ioc(Inversion of Control)控制反转和DI
ioc意味着将你设计好的交给容器控制,而不是传统在你的对象中直接控制 谁控制了谁:传统的javaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象:而ioc是有专门一个容 ...
- MySQL_日期函数汇总
如果转载,请注明博文来源: www.cnblogs.com/xinysu/ ,版权归 博客园 苏家小萝卜 所有.望各位支持! 关于MySQL日期时间函数,每回总 ...
- 记录各种IE兼容问题,IE6,IE7,IE8,IE9,IE10
记录遇到的IE BUG: 1.IE8开发者工具打不开 解决办法:IE8新增了开发人员工具,非常不错,比早期的DevToolbar好用多了.不过在我的Win7下 使用的时候偶尔会出现一个莫名其妙的问 ...
- [js高手之路] html5 canvas系列教程 - 像素操作(反色,黑白,亮度,复古,蒙版,透明)
接着上文[js高手之路] html5 canvas系列教程 - 状态详解(save与restore),相信大家都应该玩过美颜功能,而我们今天要讲的就是canvas强大的像素处理能力,通过像素处理,实现 ...
- java注解生成xml和包含CDATA问题
百度java生成xml,有一大推的文章,主要的生成方式一种使用Dom4J ,还有一种使用Jdk自带注解类! 下面主要整理我注解类的使用,(可以参考这篇文章Dom4J生成xml和包含CDATA问题)和x ...
- 模型组合(Model Combining)之Boosting与Gradient Boosting
版权声明: 本文由LeftNotEasy发布于http://leftnoteasy.cnblogs.com, 本文可以被全部的转载或者部分使用,但请注明出处,如果有问题,请联系wheeleast@gm ...
- apache、php隐藏http头部版本信息的实现方法
1.apache隐藏头部版本信息,编辑httpd.conf文件,找到: ServerTokens OS ServerSignature On 修改为: ServerTokens ProductOnly ...
- Python实战之字符串的详细简单练习
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__' ...