IOS-RunTime应用

什么是Runtime

总结起来,iOS中的RunTime的做用有如下几点:数组

1.发送消息(obj_msgSend)缓存

2.方法交换(method_exchangeImplementations)app

3.消息转发ide

4.动态添加方法函数

5.给分类添加属性测试

6.获取到类的成员变量及其方法优化

7.动态添加类编码

8.解档与归档atom

9.字典转模型spa

 

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)去映射表查找对应的方法实现

 

 

动态添加方法

对象在收到没法解读的消息后,首先会调用所属类的 + (BOOL)resolveInstanceMethod:(SEL)sel

这个方法在运行时,没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。根据文档,若是实现了添加函数代码则返回YES,未实现返回NO。

首先从外部隐式调用一个不存在的方法:

 [person performSelector:@selector(sleep:) withObject:@"8小时"];

而后,在person对象内部重写拦截调用的方法,动态添加方法。

 1 //动态添加方法
 2 + (BOOL)resolveInstanceMethod:(SEL)sel
 3 {
 4     if ([NSStringFromSelector(sel) isEqualToString:@"sleep:"]) {
 5         class_addMethod(self, sel, (IMP)sleepMethod, "v@:*");
 6         return YES;
 7     }
 8     return [super resolveInstanceMethod:sel];
 9 }
10 void sleepMethod(id self, SEL _cmd, NSString *string)
11 {
12     NSLog(@"睡了%@",string);
13 }

其中class_addMethod的四个参数分别是:

1.Class cls 给哪一个类添加方法,本例中是self

2.SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。

3.IMP imp 方法的实现,C方法的方法实现能够直接得到。若是是OC方法,能够用+ (IMP)instanceMethodForSelector:(SEL)aSelector;得到方法的实现。

4."v@:" 意思是,v表明无返回值void,若是是i则表明int;@表明 id sel; : 表明 SEL _cmd;

  “v@:@@” 意思是,两个参数的没有返回值。

  "v@:*"意思是,表明有一个参数的方法

 

消息转发

若是在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法

消息继续往下传递到- (id)forwardingTargetForSelector:(SEL)aSelector看看是否是有对象能够执行这个方法

 1 + (BOOL)resolveInstanceMethod:(SEL)sel {
 2  
 3     return [super resolveInstanceMethod:sel];
 4 }
 5 
 6 
 7 //消息转发
 8 - (id)forwardingTargetForSelector:(SEL)aSelector
 9 {
10     Class class = NSClassFromString(@"Chinese");
11     Person *person = [[class alloc] init];
12     if (aSelector == NSSelectorFromString(@"study")) {
13         return person;
14     }
15     return [super forwardingTargetForSelector:aSelector];
16 }

 

动态交换方法

 1 //动态交换方法
 2 + (void)load
 3 {
 4     /*
 5      load方法会在类第一次加载时调用
 6      交换方法应该保证,在程序中只被执行一次
 7      */
 8     
 9     SEL runSEL = @selector(run);
10     SEL eatSEL = @selector(eat);
11     
12     //两个方法的Method方法地址
13     Method runMethod = class_getInstanceMethod([self class], runSEL);
14     Method eatMethod = class_getInstanceMethod([self class], eatSEL);
15     
16     //首先动态的添加方法,实现是被交换的方法,返回值表示添加成功仍是失败
17     BOOL isAdd = class_addMethod(self, eatSEL, method_getImplementation(runMethod), "v@:");
18     
19     if (isAdd) {
20         //若是成功说明类中不存在这个方法实现,将被交换的方法实现替换这个并不存在的实现
21         class_replaceMethod(self, runSEL, method_getImplementation(eatMethod), "v@:");
22     } else {
23         method_exchangeImplementations(runMethod, eatMethod);
24     }
25 }

 

给分类添加属性

 1 #import "Person.h"
 2 
 3 @interface Person (Ext)
 4 
 5 @property (nonatomic, strong) NSString *IDCard;
 6 
 7 @end
 8 
 9 
10 //
11 //  Person+Ext.m
12 //  IOS_0423_RunTime
13 //
14 //  Created by ma c on 16/4/24.
15 //  Copyright © 2016年 博文科技. All rights reserved.
16 //
17 
18 #import "Person+Ext.h"
19 #import <objc/message.h>
20 
21 @implementation Person (Ext)
22 
23 static const char *key = "identifier";
24 
25 - (void)setIDCard:(NSString *)IDCard
26 {
27     // 第一个参数:给哪一个对象添加关联
28     // 第二个参数:关联的key,经过这个key获取
29     // 第三个参数:关联的value
30     // 第四个参数:关联的策略
31     objc_setAssociatedObject(self, key, IDCard, OBJC_ASSOCIATION_COPY_NONATOMIC);
32 
33 }
34 
35 - (NSString *)IDCard
36 {
37     // 根据关联的key,获取关联的值。
38     return objc_getAssociatedObject(self, key);
39 }
40 
41 @end

 

动态建立类

  1 #import "AppDelegate.h"
  2 #import <objc/runtime.h>
  3 
  4 @interface AppDelegate ()
  5 
  6 @end
  7 
  8 @implementation AppDelegate
  9 
 10 
 11 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 12     // 延时,等待全部控件加载完
 13     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 14         [self test];
 15     });
 16     return YES;
 17 }
 18 
 19 - (void)test
 20 {
 21     // 这个规则确定事先跟服务端沟通好,跳转对应的界面须要对应的参数
 22     NSDictionary *userInfo = @{
 23                                @"class": @"BowenViewController",
 24                                @"property": @{
 25                                        @"ID": @"123",
 26                                        @"type": @"12"
 27                                        }
 28                                };
 29     
 30     [self push:userInfo];
 31 }
 32 
 33 #pragma mark 接收推送消息
 34 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
 35 {
 36 
 37     [self push:userInfo];
 38 }
 39 
 40 /**
 41  *  跳转界面
 42  */
 43 - (void)push:(NSDictionary *)params
 44 {
 45     // 类名
 46     NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
 47     const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
 48     
 49     // 从一个字串返回一个类
 50     Class newClass = objc_getClass(className);
 51     if (!newClass)
 52     {
 53         // 建立一个类
 54         Class superClass = [NSObject class];
 55         newClass = objc_allocateClassPair(superClass, className, 0);
 56         // 注册你建立的这个类
 57         objc_registerClassPair(newClass);
 58     }
 59     // 建立对象
 60     id instance = [[newClass alloc] init];
 61     
 62     NSDictionary *propertys = params[@"property"];
 63     [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 64         // 检测这个对象是否存在该属性
 65         if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
 66             // 利用kvc赋值
 67             [instance setValue:obj forKey:key];
 68         }
 69     }];
 70 
 71     
 72     // 获取导航控制器
 73     UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
 74     UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
 75     
 76     // 跳转到对应的控制器
 77     [pushClassStance pushViewController:instance animated:YES];
 78 }
 79 
 80 /**
 81  *  检测对象是否存在该属性
 82  */
 83 - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
 84 {
 85     unsigned int outCount, i;
 86     
 87     // 获取对象里的属性列表
 88     objc_property_t * properties = class_copyPropertyList([instance
 89                                                            class], &outCount);
 90     
 91     for (i = 0; i < outCount; i++) {
 92         objc_property_t property =properties[i];
 93         //  属性名转成字符串
 94         NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
 95         // 判断该属性是否存在
 96         if ([propertyName isEqualToString:verifyPropertyName]) {
 97             free(properties);
 98             return YES;
 99         }
100     }
101     free(properties);
102     
103     return NO;
104 }
105 
106 @end

 

解档与归档

 1 //归档
 2 - (void)encodeWithCoder:(NSCoder *)aCoder
 3 {
 4     unsigned int count;
 5     //得到指向该类全部属性的指针
 6     objc_property_t *properties = class_copyPropertyList([self class], &count);
 7     
 8     for (int i = 0; i < count; i++) {
 9         //得到该类一个属性的指针
10         objc_property_t property = properties[i];
11         
12         //得到属性的名称
13         const char *nameC = property_getName(property);
14         //C的字符串转成OC字符串
15         NSString *nameOC = [NSString stringWithUTF8String:nameC];
16         
17         //经过关键字取值
18         NSString *propertyValue = [self valueForKey:nameOC];
19         //编码属性
20         [aCoder encodeObject:propertyValue forKey:nameOC];
21     }
22     free(properties);
23 
24     
25 }
26 
27 //解档
28 - (instancetype)initWithCoder:(NSCoder *)aDecoder
29 {
30     unsigned int count;
31     //得到指向该类全部属性的指针
32     objc_property_t *properties = class_copyPropertyList([self class], &count);
33     
34     for (int i = 0; i < count; i++) {
35         //得到该类一个属性的指针
36         objc_property_t property = properties[i];
37         
38         //得到属性的名称
39         const char *nameC = property_getName(property);
40         //C的字符串转成OC字符串
41         NSString *nameOC = [NSString stringWithUTF8String:nameC];
42         //解码属性值
43         NSString *propertyValue = [aDecoder decodeObjectForKey:nameOC];
44         [self setValue:propertyValue forKey:nameOC];
45     }
46     free(properties);
47     
48     return self;
49 }

 

字典转模型

思路:利用运行时,遍历模型中全部属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。

步骤:提供一个NSObject分类,专门字典转模型,之后全部模型均可以经过这个分类转。

  1 @implementation ViewController
  2 
  3 - (void)viewDidLoad {
  4     [super viewDidLoad];
  5     // Do any additional setup after loading the view, typically from a nib.
  6 
  7     // 解析Plist文件
  8     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
  9 
 10     NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
 11 
 12     // 获取字典数组
 13     NSArray *dictArr = statusDict[@"statuses"];
 14 
 15     // 自动生成模型的属性字符串
 16 //    [NSObject resolveDict:dictArr[0][@"user"]];
 17 
 18 
 19     _statuses = [NSMutableArray array];
 20 
 21     // 遍历字典数组
 22     for (NSDictionary *dict in dictArr) {
 23 
 24         Status *status = [Status modelWithDict:dict];
 25 
 26         [_statuses addObject:status];
 27 
 28     }
 29 
 30     // 测试数据
 31     NSLog(@"%@ %@",_statuses,[_statuses[0] user]);
 32 
 33 
 34 }
 35 
 36 @end
 37 
 38 @implementation NSObject (Model)
 39 
 40 + (instancetype)modelWithDict:(NSDictionary *)dict
 41 {
 42     // 思路:遍历模型中全部属性-》使用运行时
 43 
 44     // 0.建立对应的对象
 45     id objc = [[self alloc] init];
 46 
 47     // 1.利用runtime给对象中的成员属性赋值
 48 
 49     // class_copyIvarList:获取类中的全部成员属性
 50     // Ivar:成员属性的意思
 51     // 第一个参数:表示获取哪一个类中的成员属性
 52     // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
 53     // 返回值Ivar *:指的是一个ivar数组,会把全部成员属性放在一个数组中,经过返回的数组就能所有获取到。
 54     /* 相似下面这种写法
 55 
 56      Ivar ivar;
 57      Ivar ivar1;
 58      Ivar ivar2;
 59      // 定义一个ivar的数组a
 60      Ivar a[] = {ivar,ivar1,ivar2};
 61 
 62      // 用一个Ivar *指针指向数组第一个元素
 63      Ivar *ivarList = a;
 64 
 65      // 根据指针访问数组第一个元素
 66      ivarList[0];
 67 
 68      */
 69     unsigned int count;
 70 
 71     // 获取类中的全部成员属性
 72     Ivar *ivarList = class_copyIvarList(self, &count);
 73 
 74     for (int i = 0; i < count; i++) {
 75         // 根据角标,从数组取出对应的成员属性
 76         Ivar ivar = ivarList[i];
 77 
 78         // 获取成员属性名
 79         NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
 80 
 81         // 处理成员属性名->字典中的key
 82         // 从第一个角标开始截取
 83         NSString *key = [name substringFromIndex:1];
 84 
 85         // 根据成员属性名去字典中查找对应的value
 86         id value = dict[key];
 87 
 88         // 二级转换:若是字典中还有字典,也须要把对应的字典转换成模型
 89         // 判断下value是不是字典
 90         if ([value isKindOfClass:[NSDictionary class]]) {
 91             // 字典转模型
 92             // 获取模型的类对象,调用modelWithDict
 93             // 模型的类名已知,就是成员属性的类型
 94 
 95             // 获取成员属性类型
 96            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
 97           // 生成的是这种@"@\"User\"" 类型 -》 @"User"  在OC字符串中 \" -> ",\是转义的意思,不占用字符
 98             // 裁剪类型字符串
 99             NSRange range = [type rangeOfString:@"\""];
100 
101            type = [type substringFromIndex:range.location + range.length];
102 
103             range = [type rangeOfString:@"\""];
104 
105             // 裁剪到哪一个角标,不包括当前角标
106           type = [type substringToIndex:range.location];
107 
108 
109             // 根据字符串类名生成类对象
110             Class modelClass = NSClassFromString(type);
111 
112 
113             if (modelClass) { // 有对应的模型才须要转
114 
115                 // 把字典转模型
116                 value  =  [modelClass modelWithDict:value];
117             }
118 
119 
120         }
121 
122         // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
123         // 判断值是不是数组
124         if ([value isKindOfClass:[NSArray class]]) {
125             // 判断对应类有没有实现字典数组转模型数组的协议
126             if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
127 
128                 // 转换成id类型,就能调用任何对象的方法
129                 id idSelf = self;
130 
131                 // 获取数组中字典对应的模型
132                 NSString *type =  [idSelf arrayContainModelClass][key];
133 
134                 // 生成模型
135                Class classModel = NSClassFromString(type);
136                 NSMutableArray *arrM = [NSMutableArray array];
137                 // 遍历字典数组,生成模型数组
138                 for (NSDictionary *dict in value) {
139                     // 字典转模型
140                   id model =  [classModel modelWithDict:dict];
141                     [arrM addObject:model];
142                 }
143 
144                 // 把模型数组赋值给value
145                 value = arrM;
146 
147             }
148         }
149 
150 
151         if (value) { // 有值,才须要给模型的属性赋值
152             // 利用KVC给模型中的属性赋值
153             [objc setValue:value forKey:key];
154         }
155 
156     }
157 
158     return objc;
159 }
160 
161 @end

 

其余:http://www.jianshu.com/p/58c985408b75

相关文章
相关标签/搜索