码迷,mamicode.com
首页 > 移动开发 > 详细

IOS-RunTime

时间:2016-04-24 15:42:37      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

什么是Runtime

总结起来,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

发送消息

消息机制

  • objc_msgSend,只有对象才能发送消息,因此以objc开头.
  • 使用消息机制的前提:导入#improt<objc/message.h>
     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指针指向的对象(也就是元类对象)中操作。

  1. 首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
  2. 如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
  3. 如果没找到,去父类指针所指向的对象中执行1,2.
  4. 以此类推,如果一直到根类还没找到,转向拦截调用。
  5. 如果没有重写拦截调用的方法,程序报错。

以上的过程给我带来的启发:

  • 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
  • 如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。

拦截调用(消息转发)

在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

1 + (BOOL)resolveClassMethod:(SEL)sel;
2 + (BOOL)resolveInstanceMethod:(SEL)sel;
3 //后两个方法需要转发到其他的类处理
4 - (id)forwardingTargetForSelector:(SEL)aSelector;
5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
  • 第二个方法和第一个方法相似,只不过处理的是实例方法。
  • 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
  • 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。 

1、动态方法解析

+ (BOOL)resolveInstanceMethod:(SEL)sel;

首先,当接受到未能识别的选择子时,运行时系统会调用该函数用以给对象一次机会来添加相应的方法实现,如果用户在该函数中动态添加了相应方法的实现,则跳转到方法的实现部分,并将该实现存入缓存中,以供下次调用。

2、备援接收者

- (id)forwardingTargetForSelector:(SEL)aSelector;

如果运行时在消息转发的第一步中未找到所调用方法的实现,那么当前接收者还有第二次机会进行未知选择子的处理。这时运行期系统会调用上述方法,并将未知选择子作为参数传入,该方法可以返回一个能处理该选择子的对象,运行时系统会根据返回的对象进行查找,若找到则跳转到相应方法的实现,则消息转发结束。

3、完整的消息转发

- (void)forwardInvocation:(NSInvocation *)anInvocation;

当运行时系统检测到第二步中用户未返回能处理相应选择子的对象时,那么来到这一步就要启动完整的消息转发机制了。该方法可以改变消息调用目标,运行时系统根据所改变的调用目标,向调用目标方法列表中查询对应方法的实现并实现跳转,这种方式和第二步的操作非常相似。当然你也可以修改方法的选择子,亦或者向所调用方法中追加一个参数等来跳转到相关方法的实现。

最后,如果消息转发的第三步还未能处理该未知选择子的话,那么最终会调用NSObject类的如下方法用以异常的抛出,表明该选择子最终未能处理。

- (void)doesNotRecognizeSelector:(SEL)aSelector;


IOS-RunTime

标签:

原文地址:http://www.cnblogs.com/oc-bowen/p/5426968.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!