标签:
总结起来,iOS中的RunTime的作用有以下几点:
1.发送消息(obj_msgSend)
2.方法交换(method_exchangeImplementations)
3.消息转发
4.动态添加方法
5.给分类添加属性
6.获取到类的成员变量及其方法
7.动态添加类
runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。
在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者.例如[target doSomething];
会被转化成objc_msgSend(target, @selector(doSomething));
。
OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。
例如: OC就是典型的运行时机制,OC属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用.而C语言中函数在编译的时候就会决定调用哪个函数.
相关的定义:
1 /// 描述类中的一个方法 2 typedef struct objc_method *Method; 3 4 /// 实例变量 5 typedef struct objc_ivar *Ivar; 6 7 /// 类别Category 8 typedef struct objc_category *Category; 9 10 /// 类中声明的属性 11 typedef struct objc_property *objc_property_t;
类在runtime中的表示
1 //类在runtime中的表示 2 struct objc_class { 3 Class isa;//指针,顾名思义,表示是一个什么, 4 //实例的isa指向类对象,类对象的isa指向元类 5 6 #if !__OBJC2__ 7 Class super_class; //指向父类 8 const char *name; //类名 9 long version; 10 long info; 11 long instance_size 12 struct objc_ivar_list *ivars //成员变量列表 13 struct objc_method_list **methodLists; //方法列表 14 struct objc_cache *cache;//缓存 15 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存 16 struct objc_protocol_list *protocols //协议列表 17 #endif 18 } OBJC2_UNAVAILABLE; 19 /* Use `Class` instead of `struct objc_class *` */
有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。
1 // 2 // RunTimeTool.m 3 // IOS_0423_RunTime 4 // 5 // Created by ma c on 16/4/23. 6 // Copyright © 2016年 博文科技. All rights reserved. 7 // 8 9 #import "RunTimeTool.h" 10 #import <objc/runtime.h> 11 #import "Person.h" 12 13 @implementation RunTimeTool 14 15 //获得成员变量 16 + (void)accessToMemberVariable 17 { 18 unsigned int count; 19 //获得成员变量结构体 20 Ivar *ivars = class_copyIvarList([Person class], &count); 21 for (int i = 0; i < count; i++) { 22 Ivar ivar = ivars[i]; 23 24 //根据Ivar获得成员变量的名称 25 const char *nameC = ivar_getName(ivar); 26 //C的字符串转成OC字符串 27 NSString *nameOC = [NSString stringWithUTF8String:nameC]; 28 NSLog(@"%@",nameOC); 29 } 30 free(ivars); 31 } 32 //获得属性 33 + (void)accessToProperty 34 { 35 unsigned int count; 36 //获得指向该类所有属性的指针 37 objc_property_t *properties = class_copyPropertyList([Person class], &count); 38 39 for (int i = 0; i < count; i++) { 40 //获得该类一个属性的指针 41 objc_property_t property = properties[i]; 42 43 //获得属性的名称 44 const char *nameC = property_getName(property); 45 //C的字符串转成OC字符串 46 NSString *nameOC = [NSString stringWithUTF8String:nameC]; 47 NSLog(@"%@",nameOC); 48 } 49 free(properties); 50 } 51 //获得方法 52 + (void)accessToMethod 53 { 54 unsigned int count; 55 //获得指向该类所有方法的指针 56 Method *methods = class_copyMethodList([Person class], &count); 57 58 for (int i = 0; i < count; i++) { 59 60 //获得该类的一个方法指针 61 Method method = methods[i]; 62 //获取方法 63 SEL methodSEL = method_getName(method); 64 //将方法名转化成字符串 65 const char *methodC = sel_getName(methodSEL); 66 //C的字符串转成OC字符串 67 NSString *methodOC = [NSString stringWithUTF8String:methodC]; 68 //获得方法参数个数 69 int arguments = method_getNumberOfArguments(method); 70 NSLog(@"%@方法的参数个数:%d",methodOC, arguments); 71 } 72 free(methods); 73 } 74 //获得协议 75 + (void)accessToProtocol 76 { 77 unsigned int count; 78 //获取指向该类遵循的所有协议的指针 79 __unsafe_unretained Protocol **protocols = class_copyProtocolList([Person class], &count); 80 81 for (int i = 0; i < count; i++) { 82 //获取指向该类遵循的一个协议的指针 83 Protocol *protocol = protocols[i]; 84 85 //获得属性的名称 86 const char *nameC = protocol_getName(protocol); 87 //C的字符串转成OC字符串 88 NSString *nameOC = [NSString stringWithUTF8String:nameC]; 89 NSLog(@"%@",nameOC); 90 91 } 92 free(protocols); 93 } 94 95 96 @end
消息机制
1 // 创建person对象 2 Person *p = [[Person alloc] init]; 3 4 // 调用对象方法 5 [p eat]; 6 7 // 本质:让对象发送消息 8 objc_msgSend(p, @selector(eat)); 9 10 // 调用类方法的方式:两种 11 // 第一种通过类名调用 12 [Person eat]; 13 // 第二种通过类对象调用 14 [[Person class] eat]; 15 16 // 用类名调用类方法,底层会自动把类名转换成类对象调用 17 // 本质:让类对象发送消息 18 objc_msgSend([Person class], @selector(eat));
消息机制原理:对象根据方法编号(SEL)去映射表查找对应的方法实现
让我们看一下方法调用在运行时的过程(参照前文类在runtime中的表示)
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
以上的过程给我带来的启发:
super
这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject
的四个方法来处理。
1 + (BOOL)resolveClassMethod:(SEL)sel; 2 + (BOOL)resolveInstanceMethod:(SEL)sel; 3 //后两个方法需要转发到其他的类处理 4 - (id)forwardingTargetForSelector:(SEL)aSelector; 5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
NSInvocation
传给你。做完你自己的处理后,调用invokeWithTarget:
方法让某个target触发这个方法。 1、动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel;
首先,当接受到未能识别的选择子时,运行时系统会调用该函数用以给对象一次机会来添加相应的方法实现,如果用户在该函数中动态添加了相应方法的实现,则跳转到方法的实现部分,并将该实现存入缓存中,以供下次调用。
2、备援接收者
- (id)forwardingTargetForSelector:(SEL)aSelector;
如果运行时在消息转发的第一步中未找到所调用方法的实现,那么当前接收者还有第二次机会进行未知选择子的处理。这时运行期系统会调用上述方法,并将未知选择子作为参数传入,该方法可以返回一个能处理该选择子的对象,运行时系统会根据返回的对象进行查找,若找到则跳转到相应方法的实现,则消息转发结束。
3、完整的消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation;
当运行时系统检测到第二步中用户未返回能处理相应选择子的对象时,那么来到这一步就要启动完整的消息转发机制了。该方法可以改变消息调用目标,运行时系统根据所改变的调用目标,向调用目标方法列表中查询对应方法的实现并实现跳转,这种方式和第二步的操作非常相似。当然你也可以修改方法的选择子,亦或者向所调用方法中追加一个参数等来跳转到相关方法的实现。
最后,如果消息转发的第三步还未能处理该未知选择子的话,那么最终会调用NSObject类的如下方法用以异常的抛出,表明该选择子最终未能处理。
- (void)doesNotRecognizeSelector:(SEL)aSelector;
标签:
原文地址:http://www.cnblogs.com/oc-bowen/p/5426968.html