用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block)。开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做"消息传递"(Messaging)。当程序运行起来以后,为其提供相关支持的代码叫做"Objective-C运行期环境"(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。即我们写的oc代码,它在运行的时候是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。

  那什么是Objective-C runtime?
  简单来说,Objective-C runtime是一个实现Objective-C语言的C库。对象可以用C语言中的结构体表示,而方法(methods)可以用C函数实现。事实上,他们差不多也是这么干了,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类,对象和它们的方法。
  runtime的常见方法
   //返回一个指向类的成员变量数组的指针
class_copyIvarList()
//返回一个指向类的属性数组的指针
class_copyPropertyList()
注意:根据Apple官方runtime.h文档所示,上面两个方法返回的指针,在使用完毕之后必须free()。
---------------------------------------------------
//获取成员变量名-->C类型的字符串
ivar_getName()
//获取属性名-->C类型的字符串
property_getName()
---------------------------------------------------
typedef struct objc_method *Method;
class_getInstanceMethod()
//以上两个函数传入返回Method类型
class_getClassMethod()
---------------------------------------------------
//交换两个方法的实现
method_exchangeImplementations()

  

  runtime在开发中的用途

1.动态的遍历一个类的所有成员变量,属性,方法,协议等,可用于字典转模型,归档解档操作

    unsigned int count;
//获取成员变量的结构体
Ivar *ivars = class_copyIvarList([Person class], &count); for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
//根据ivar获得其成员变量的名称
const char *name = ivar_getName(ivar);
//C的字符串转OC的字符串
NSString *key = [NSString stringWithUTF8String:name];
NSLog(@"%d == %@",i,key);
}
//记得释放
free(ivars);   //获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([Person class], &count); for (int i = 0; i < count; i++) {
//获得该类的一个属性的指针
objc_property_t property = properties[i];
//获取属性的名称
const char *name = property_getName(property);
//将C的字符串转为OC的
NSString *key = [NSString stringWithUTF8String:name]; NSLog(@"%d == %@",i,key);
}
//记得释放
free(properties);   //获取指向该类所有方法的指针
Method *methods = class_copyMethodList([Person class], &count); for (int i = 0; i < count; i++) {
//获取该类的一个方法的指针
Method method = methods[i];
//获取方法
SEL methodSEL = method_getName(method);
//将方法转换为C字符串
const char *name = sel_getName(methodSEL);
//将C字符串转为OC字符串
NSString *methodName = [NSString stringWithUTF8String:name]; //获取方法参数个数
int arguments = method_getNumberOfArguments(method); NSLog(@"%d == %@ %d",i,methodName,arguments);
}
//记得释放
free(methods);   //获取指向该类遵循的所有协议的指针
__unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &count); for (int i = 0; i < count; i++) {
//获取该类遵循的一个协议指针
Protocol *protocol = protocols[i];
//获取C字符串协议名
const char *name = protocol_getName(protocol);
//C字符串转OC字符串
NSString *protocolName = [NSString stringWithUTF8String:name];
NSLog(@"%d == %@",i,protocolName);
}
//记得释放
free(protocols);

应用场景:

  • 可以利用遍历类的属性,来快速的进行归档操作。
  • 将从网络上下载的json数据进行字典转模型。

//注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count;
//获得指向当前类的所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class], &count); for (int i = 0; i < count; i++) {
//获取指向当前类的一个属性的指针
objc_property_t property = properties[i];
//获取C字符串属性名
const char *name = property_getName(property);
//C字符串转OC字符串
NSString *propertyName = [NSString stringWithUTF8String:name];
//通过关键词取值
NSString *propertyValue = [self valueForKey:propertyName];
//编码属性
[aCoder encodeObject:propertyValue forKey:propertyName];
}
//记得释放
free(properties);
} - (instancetype)initWithCoder:(NSCoder *)aDecoder {
unsigned int count;
//获得指向当前类的所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class], &count); for (int i = 0; i < count; i++) {
//获取指向当前类的一个属性的指针
objc_property_t property = properties[i];
//获取C字符串属性名
const char *name = property_getName(property);
//C字符串转OC字符串
NSString *propertyName = [NSString stringWithUTF8String:name];
//解码属性值
NSString *propertyValue = [aDecoder decodeObjectForKey:propertyName];
[self setValue:propertyValue forKey:propertyName];
}
//记得释放
free(properties);
return self;
}
//重写description方法,打印出自定义的属性
- (NSString *)description{
    NSString *string = [NSString stringWithFormat:@"name=%@ \n age=%d \n apples=%@",_name,_age,_apples];
    return string;
}

  归档解档方法

        //自定义类
     Person *p = [[Person alloc] init];
p.name = @"张三";
p.age = 18;
p.apples = @[@"iphone",@"ipad"]; //归档
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"person.archiver"];
BOOL success = [NSKeyedArchiver archiveRootObject:p toFile:filePath];
if(success){
NSLog(@"归档成功");
}
//解归档
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@",person);

字典转模型

#import "NSObject+Item.h"
#import <objc/message.h> @implementation NSObject (Item) // 字典转模型
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
// 创建对应模型对象
id objc = [[self alloc] init];
unsigned int count = 0; // 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count); // 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
for (int i = 0; i < count; i++) { // 2.1 获取成员属性
Ivar ivar = ivarList[i]; // 2.2 获取成员属性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 2.3 _成员属性名 => 字典key
NSString *key = [ivarName substringFromIndex:1]; // 2.4 去字典中取出对应value给模型属性赋值
id value = dict[key]; // 获取成员属性类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 二级转换,字典中还有字典,也需要把对应字典转换成模型
//
// 判断下value,是不是字典
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
       // 是字典对象,并且属性名对应类型是自定义类型
// user User // 处理类型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
     //自定义对象,并且值是字典
// value :user 字典 -> User模型
    // 获取模型(user)类对象
    Class modalClass = NSClassFromString(ivarType);     //字典转模型
    if(modalClass){

        value = [modalClass objectWithDict: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 objectWithDict:dict];
[arrM addObject:model];
} // 把模型数组赋值给value
value = arrM; }
} // 2.5 KVC字典转模型
if (value) { [objc setValue:value forKey:key];
}
} // 返回对象
return objc; } @end

2.交换方法,可以是不同类的方法,也可以是同类的

   Method one =  class_getInstanceMethod([Person0 class], @selector(oneMethod));

    Method two =  class_getInstanceMethod([Person1 class], @selector(twoMethod));

    method_exchangeImplementations(one, two);

3.添加方法

- (void)sayFrom
{ class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
if ([self.person respondsToSelector:@selector(guess)]) {
//Method method = class_getInstanceMethod([self.xiaoMing class], @selector(guess));
[self.person performSelector:@selector(guess)]; } else{
NSLog(@"Sorry,I don't know");
}
self.textview.text = @"beijing";
} void guessAnswer(id self,SEL _cmd){ NSLog(@"i am from beijing"); }

4.拦截调用动态添加

/*
*  + (BOOL)resolveClassMethod:(SEL)sel;//调用不存在的类方法时返回No,可以加上自己的处理,返回Yes
*  + (BOOL)resolveInstanceMethod:(SEL)sel;//跟上面的类似,不过处理的是实例方法
*/ //首先从外部隐式调用一个不存在的方法:
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

//然后,在target对象内部重写拦截调用的方法,动态添加方法。
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}

5.方法上加功能

@implementation UIButton(count)
//load方法会在类第一次被加载的时候调用
+ (void)load { static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ Class selfClass = [self class]; SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL); SEL cusSEL = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL); BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
method_exchangeImplementations(oriMethod, cusMethod);
} });
} - (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[[Tool sharedManager] addCount];
[self mySendAction:action to:target forEvent:event];
}

6.动态变量控制

  unsigned int count = 0;
Ivar *ivar = class_copyIvarList([self.person class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivar[i];
const char *varName = ivar_getName(var);
NSString *proname = [NSString stringWithUTF8String:varName]; if ([proname isEqualToString:@"_name"]) { //这里别忘了给属性加下划线
object_setIvar(self.person, var, @"daming");//使用runtime将.name属性的值修改
break;
}
}
NSLog(@"XiaoMing change name is %@",self.person.name);

7.拓展属性

/*
*在开发中经常需要给已有的类添加方法和属性,但是Objective-C是不允许给已有类通过分类添加属性的,因为类分类是不会自动生成成员变量的。但是,我们可以通过运行时机制就可以做到了。
*/
@interface NSObject(height)
//头文件中声明一个属性
@property (nonatomic, assign) double height;
@end @implementation NSObject(height)
static double heightKey;//用来参考 -(void)setHeight:(double)height
{ /*设置关联值(Setter)
*  void objc_setAssociatedObject(id object, const void *key,id value,objc_AssociationPolicy)
*  object:与谁关联,通常都是传self
*  key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
*  value: 关联所设置的值

*  policy:内存管理策略,比如使用copy

*/
    objc_setAssociatedObject(self, &heightKey, @(height), OBJC_ASSOCIATION_ASSIGN);
} -(double)height
{ /*获取关联值(Getter)
*  id objc_getAssociatedObject(id object, const void *key)
*  object:与谁关联,通常都是传self
*  key:唯一键,在设置关联时所使用的键
*/ return [objc_getAssociatedObject(self, &heightKey) doubleValue];
} @end

以上是关于runtime一些常见的用法,关于详细的原理介绍推荐阅读以下博客:

Runtime快速入门

Runtime运行时特性详解

Objective-C Runtime 运行时

最新文章

  1. 如何解决sublime text 2总是在新窗口中打开文件及文件夹
  2. Html5 舞动的雨伞
  3. clean code meaningful names
  4. ftp如何预览图片 解决方案
  5. js判断ie11浏览器
  6. Android源码下载
  7. js实现弹出窗口、页面变成灰色并不可操作的例子
  8. APP在线课程设计
  9. 自定义编写js格式化数字的函数
  10. mysql 原理 ~ 二阶段提交协议通说
  11. 步步为营-64-进程&amp;线程
  12. 080 HBase的属性
  13. CentOS 下安装 Node.js 8.11.3 LTS Version
  14. [Writeup]百度一下,你就知道
  15. Java控制并发线程数的Semaphore
  16. Brief History of Machine Learning
  17. Sails入门指南
  18. Mint linux中调整屏幕亮度的方法
  19. /etc/fstab 文件解析
  20. 【数组】3Sum

热门文章

  1. RabbitMq应用二
  2. JS 判断数据类型的三种方法
  3. 开源:ASP.NET Aries 开发框架
  4. C# 中参数验证方式的演变
  5. Partition2:对表分区
  6. 从零开始编写自己的C#框架(25)——网站部署
  7. CSS 3学习——transform 2D转换
  8. JS魔法堂:不完全国际化&amp;本地化手册 之 理論篇
  9. Android之SharedPreferences数据存储
  10. iOS---&gt;微信支付小结