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

相關文章
相關標籤/搜索