總結起來,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