目录html
1.RunTime 概述ios
咱们在面试的时候,常常都会被问到这么个问题:为何说OC是一门动态的语言???其实也就是想知道你对runtime的了解程度。c++
2.RunTime消息机制git
1.消息机制是运行时里面最重要的机制,OC中任何方法的调用,本质都是发送消息。eg:github
当咱们实例化这个对象时:MyClass *object = [[MyClass alloc] init]; 就会调这个实例化方法:[object showUserName];面试
咱们大概来看一下它的底层实现:json
证实过程:数组
在终端用命令打开此类文件所在的文件夹,继续写入命令:clang -rewrite-objc MyClass.m(把oc代码转写成xcode
c/c++代码,咱们经常使用它来窥探OC的底层实现),不一会在原来的同一目录下会多出一个 MyClass.cpp 文件app
双击打开,能够看到 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;
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);
获取关联对象使用
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
因为本身以前查阅资料时,没有及时写总结,如今有点闲时了才开始写,因此有些参考文章都不记得了,请相关做者见谅。。。