IOS-RunTime应用
什么是Runtime
总结起来,iOS中的RunTime的作用有以下几点:
1.发送消息(obj_msgSend)
2.方法交换(method_exchangeImplementations)
3.消息转发
4.动态添加方法
5.给分类添加属性
6.获取到类的成员变量及其方法
7.动态添加类
8.解档与归档
9.字典转模型
runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。
在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者.例如[target doSomething];
会被转化成objc_msgSend(target, @selector(doSomething));
。
OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。
例如: OC就是典型的运行时机制,OC属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用.而C语言中函数在编译的时候就会决定调用哪个函数.
相关的定义:
/// 描述类中的一个方法
typedef struct objc_method *Method; /// 实例变量
typedef struct objc_ivar *Ivar; /// 类别Category
typedef struct objc_category *Category; /// 类中声明的属性
typedef struct objc_property *objc_property_t;
类在runtime中的表示
//类在runtime中的表示
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类 #if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
获取列表
有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。
//
// RunTimeTool.m
// IOS_0423_RunTime
//
// Created by ma c on 16/4/23.
// Copyright © 2016年 博文科技. All rights reserved.
// #import "RunTimeTool.h"
#import <objc/runtime.h>
#import "Person.h" @implementation RunTimeTool //获得成员变量
+ (void)accessToMemberVariable
{
unsigned int count;
//获得成员变量结构体
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = ; i < count; i++) {
Ivar ivar = ivars[i]; //根据Ivar获得成员变量的名称
const char *nameC = ivar_getName(ivar);
//C的字符串转成OC字符串
NSString *nameOC = [NSString stringWithUTF8String:nameC];
NSLog(@"%@",nameOC);
}
free(ivars);
}
//获得属性
+ (void)accessToProperty
{
unsigned int count;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([Person class], &count); for (int i = ; i < count; i++) {
//获得该类一个属性的指针
objc_property_t property = properties[i]; //获得属性的名称
const char *nameC = property_getName(property);
//C的字符串转成OC字符串
NSString *nameOC = [NSString stringWithUTF8String:nameC];
NSLog(@"%@",nameOC);
}
free(properties);
}
//获得方法
+ (void)accessToMethod
{
unsigned int count;
//获得指向该类所有方法的指针
Method *methods = class_copyMethodList([Person class], &count); for (int i = ; i < count; i++) { //获得该类的一个方法指针
Method method = methods[i];
//获取方法
SEL methodSEL = method_getName(method);
//将方法名转化成字符串
const char *methodC = sel_getName(methodSEL);
//C的字符串转成OC字符串
NSString *methodOC = [NSString stringWithUTF8String:methodC];
//获得方法参数个数
int arguments = method_getNumberOfArguments(method);
NSLog(@"%@方法的参数个数:%d",methodOC, arguments);
}
free(methods);
}
//获得协议
+ (void)accessToProtocol
{
unsigned int count;
//获取指向该类遵循的所有协议的指针
__unsafe_unretained Protocol **protocols = class_copyProtocolList([Person class], &count); for (int i = ; i < count; i++) {
//获取指向该类遵循的一个协议的指针
Protocol *protocol = protocols[i]; //获得属性的名称
const char *nameC = protocol_getName(protocol);
//C的字符串转成OC字符串
NSString *nameOC = [NSString stringWithUTF8String:nameC];
NSLog(@"%@",nameOC); }
free(protocols);
} @end
发送消息
objc_msgSend,只有对象才能发送消息,因此以objc开头.
使用消息机制的前提:导入#improt<objc/message.h>
// 创建person对象
Person *p = [[Person alloc] init]; // 调用对象方法
[p eat]; // 本质:让对象发送消息
objc_msgSend(p, @selector(eat)); // 调用类方法的方式:两种
// 第一种通过类名调用
[Person eat];
// 第二种通过类对象调用
[[Person class] eat]; // 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
消息机制原理:对象根据方法编号(SEL)去映射表查找对应的方法实现
动态添加方法
对象在收到无法解读的消息后,首先会调用所属类的 + (BOOL)resolveInstanceMethod:(SEL)sel
这个方法在运行时,没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。
首先从外部隐式调用一个不存在的方法:
[person performSelector:@selector(sleep:) withObject:@"8小时"];
然后,在person对象内部重写拦截调用的方法,动态添加方法。
//动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"sleep:"]) {
class_addMethod(self, sel, (IMP)sleepMethod, "v@:*");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void sleepMethod(id self, SEL _cmd, NSString *string)
{
NSLog(@"睡了%@",string);
}
其中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看看是不是有对象可以执行这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel { return [super resolveInstanceMethod:sel];
} //消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
Class class = NSClassFromString(@"Chinese");
Person *person = [[class alloc] init];
if (aSelector == NSSelectorFromString(@"study")) {
return person;
}
return [super forwardingTargetForSelector:aSelector];
}
动态交换方法
//动态交换方法
+ (void)load
{
/*
load方法会在类第一次加载时调用
交换方法应该保证,在程序中只被执行一次
*/ SEL runSEL = @selector(run);
SEL eatSEL = @selector(eat); //两个方法的Method方法地址
Method runMethod = class_getInstanceMethod([self class], runSEL);
Method eatMethod = class_getInstanceMethod([self class], eatSEL); //首先动态的添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod(self, eatSEL, method_getImplementation(runMethod), "v@:"); if (isAdd) {
//如果成功说明类中不存在这个方法实现,将被交换的方法实现替换这个并不存在的实现
class_replaceMethod(self, runSEL, method_getImplementation(eatMethod), "v@:");
} else {
method_exchangeImplementations(runMethod, eatMethod);
}
}
给分类添加属性
#import "Person.h" @interface Person (Ext) @property (nonatomic, strong) NSString *IDCard; @end //
// Person+Ext.m
// IOS_0423_RunTime
//
// Created by ma c on 16/4/24.
// Copyright © 2016年 博文科技. All rights reserved.
// #import "Person+Ext.h"
#import <objc/message.h> @implementation Person (Ext) static const char *key = "identifier"; - (void)setIDCard:(NSString *)IDCard
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, IDCard, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)IDCard
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
} @end
动态创建类
#import "AppDelegate.h"
#import <objc/runtime.h> @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 延时,等待所有控件加载完
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self test];
});
return YES;
} - (void)test
{
// 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
NSDictionary *userInfo = @{
@"class": @"BowenViewController",
@"property": @{
@"ID": @"",
@"type": @""
}
}; [self push:userInfo];
} #pragma mark 接收推送消息
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{ [self push:userInfo];
} /**
* 跳转界面
*/
- (void)push:(NSDictionary *)params
{
// 类名
NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding]; // 从一个字串返回一个类
Class newClass = objc_getClass(className);
if (!newClass)
{
// 创建一个类
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, );
// 注册你创建的这个类
objc_registerClassPair(newClass);
}
// 创建对象
id instance = [[newClass alloc] init]; NSDictionary *propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 检测这个对象是否存在该属性
if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用kvc赋值
[instance setValue:obj forKey:key];
}
}]; // 获取导航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex]; // 跳转到对应的控制器
[pushClassStance pushViewController:instance animated:YES];
} /**
* 检测对象是否存在该属性
*/
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i; // 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance
class], &outCount); for (i = ; i < outCount; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断该属性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties); return NO;
} @end
解档与归档
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int count;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class], &count); for (int i = ; i < count; i++) {
//获得该类一个属性的指针
objc_property_t property = properties[i]; //获得属性的名称
const char *nameC = property_getName(property);
//C的字符串转成OC字符串
NSString *nameOC = [NSString stringWithUTF8String:nameC]; //通过关键字取值
NSString *propertyValue = [self valueForKey:nameOC];
//编码属性
[aCoder encodeObject:propertyValue forKey:nameOC];
}
free(properties); } //解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
unsigned int count;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class], &count); for (int i = ; i < count; i++) {
//获得该类一个属性的指针
objc_property_t property = properties[i]; //获得属性的名称
const char *nameC = property_getName(property);
//C的字符串转成OC字符串
NSString *nameOC = [NSString stringWithUTF8String:nameC];
//解码属性值
NSString *propertyValue = [aDecoder decodeObjectForKey:nameOC];
[self setValue:propertyValue forKey:nameOC];
}
free(properties); return self;
}
字典转模型
思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。
@implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. // 解析Plist文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil]; NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath]; // 获取字典数组
NSArray *dictArr = statusDict[@"statuses"]; // 自动生成模型的属性字符串
// [NSObject resolveDict:dictArr[0][@"user"]]; _statuses = [NSMutableArray array]; // 遍历字典数组
for (NSDictionary *dict in dictArr) { Status *status = [Status modelWithDict:dict]; [_statuses addObject:status]; } // 测试数据
NSLog(@"%@ %@",_statuses,[_statuses[] user]); } @end @implementation NSObject (Model) + (instancetype)modelWithDict:(NSDictionary *)dict
{
// 思路:遍历模型中所有属性-》使用运行时 // 0.创建对应的对象
id objc = [[self alloc] init]; // 1.利用runtime给对象中的成员属性赋值 // class_copyIvarList:获取类中的所有成员属性
// Ivar:成员属性的意思
// 第一个参数:表示获取哪个类中的成员属性
// 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
// 返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
/* 类似下面这种写法 Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定义一个ivar的数组a
Ivar a[] = {ivar,ivar1,ivar2}; // 用一个Ivar *指针指向数组第一个元素
Ivar *ivarList = a; // 根据指针访问数组第一个元素
ivarList[0]; */
unsigned int count; // 获取类中的所有成员属性
Ivar *ivarList = class_copyIvarList(self, &count); for (int i = ; i < count; i++) {
// 根据角标,从数组取出对应的成员属性
Ivar ivar = ivarList[i]; // 获取成员属性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 处理成员属性名->字典中的key
// 从第一个角标开始截取
NSString *key = [name substringFromIndex:]; // 根据成员属性名去字典中查找对应的value
id value = dict[key]; // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
// 判断下value是否是字典
if ([value isKindOfClass:[NSDictionary class]]) {
// 字典转模型
// 获取模型的类对象,调用modelWithDict
// 模型的类名已知,就是成员属性的类型 // 获取成员属性类型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是这种@"@\"User\"" 类型 -》 @"User" 在OC字符串中 \" -> ",\是转义的意思,不占用字符
// 裁剪类型字符串
NSRange range = [type rangeOfString:@"\""]; type = [type substringFromIndex:range.location + range.length]; range = [type rangeOfString:@"\""]; // 裁剪到哪个角标,不包括当前角标
type = [type substringToIndex:range.location]; // 根据字符串类名生成类对象
Class modelClass = NSClassFromString(type); if (modelClass) { // 有对应的模型才需要转 // 把字典转模型
value = [modelClass modelWithDict:value];
} } // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
// 判断值是否是数组
if ([value isKindOfClass:[NSArray class]]) {
// 判断对应类有没有实现字典数组转模型数组的协议
if ([self respondsToSelector:@selector(arrayContainModelClass)]) { // 转换成id类型,就能调用任何对象的方法
id idSelf = self; // 获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key]; // 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍历字典数组,生成模型数组
for (NSDictionary *dict in value) {
// 字典转模型
id model = [classModel modelWithDict:dict];
[arrM addObject:model];
} // 把模型数组赋值给value
value = arrM; }
} if (value) { // 有值,才需要给模型的属性赋值
// 利用KVC给模型中的属性赋值
[objc setValue:value forKey:key];
} } return objc;
} @end
其他:http://www.jianshu.com/p/58c985408b75
IOS-RunTime应用的更多相关文章
- ios runtime swizzle
ios runtime swizzle @implementation NSObject(Extension) + (void)swizzleClassMethod:(Class)class orig ...
- ios runtime的相关知识
一.iOS runtime原理 对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道r ...
- iOS Runtime 实践(1)
很多时候我们都在看iOS开发中的黑魔法——Runtime.懂很多,但如何实践却少有人提及.本文便是iOS Runtime的实践第一篇. WebView 我们这次的实践主题,是使用针对接口编程的方式,借 ...
- 包建强的培训课程(11):iOS Runtime实战
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- iOS Runtime的消息转发机制
前面我们已经讲解Runtime的基本概念和基本使用,如果大家对Runtime机制不是很了解,可以先看一下以前的博客,会对理解这篇博客有所帮助!!! Runtime基本概念:https://www.cn ...
- iOS Runtime 实操练习
iOS Runtime 知识详解: http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/ 一般可以运行Runtime进行以下操作 ...
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...
- IOS runtime动态运行时二
在C#.Java中有编译时多态和运行时多态,在OC中,只有运行时的多态,这与它的运行机制有关.OC中,方法的调用是通过消息的传递来进行的.在IOS runtime动态运行时一http://www.cn ...
- iOS --runtime理解
iOS~runtime理解 Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下,一为 查阅方便二为 或许能给 ...
- iOS RunTime运行时(1):类与对象
Objective-C语言是一门动态语言,他将很多静态语言在编译和链接期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码更具有灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一下 ...
随机推荐
- java 字符串解析为json 使用org.json包的JSONObject+JSONArray
参考: https://blog.csdn.net/xingfei_work/article/details/76572550 java中四种json解析方式 JSONObject+JSONArray ...
- Redis资料汇总(转)
原文:Redis资料汇总专题 很多朋友反映,说NoSQLFan上的资料不少,但是要找到自己实用的太难,于是萌生做这样一个专题的想法.通过将不同NoSQL产品从入门到精通的各种资料进行汇总,希望能够让大 ...
- ViewPager学习及使用(一)
一:基础篇 1.ViewPager的简介和作用ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view1)ViewPager类直接继承了ViewGroup类,所有它 ...
- beego——模型定义
复杂的模型定义不是必须的,此功能用作数据库数据转换和自动建表 默认的表名规则,使用驼峰转蛇形: AuthUser -> auth_user Auth_User -> auth__user ...
- mysqlbinlog作用
mysqlbinlog:解析mysql的binlog日志 在 mysql-bin.index里面记录了所有的binlog文件,它是一个索引 binlog日志的作用:用来记录mysql内部增删改查等对m ...
- Linux用户、群组及权限
由于对文件的操作需要切换到相应文件夹下进行,所以对文件内容的修改,最基本的是需要其文件夹执行的权限. 文件夹的读权限(read)可以独立行使,但是对文件夹内容的写权限(对其内文件的新建.删除.重命名) ...
- JS之DOM对象二
前面在JS之DOM中我们知道了属性操作,下面我们来了解一下节点操作.很重要!! 一.节点操作 创建节点:var ele_a = document.createElement('a');添加节点:ele ...
- bootstrap select 学习使用笔记-------选中赋值及change监听丢失
在 bootstrap 和 knockout 共同存在下使用 select 下拉选择插件,发现绑定选项.赋值之后插件不可用了,绑定的监听事件也丢失了.迫不得已在绑定选项值之后再次调用刷新,以及赋值后重 ...
- python 对象和类
python中所有数据都是以对象形式存在.对象既包含数据(变量),也包含代码(函数),是某一类具体事物的特殊实例. 面向对象的三大特性为封装.继承和多态. 1.定义类 #定义空类 class Pers ...
- 第六课 GDB调试 (上)
1序言: 1.初学者经过学习前面的Makefile知识,信心满满,内心觉得应该要好好学习不单掌握语言的编写,也要学会相对应的工具调高开发效率.有时我们写出来的代码经过执行结果却跟我们预期不一样那怎么办 ...