Objective-C runtime的常见应用
用Objective-C等面向对象语言编程时,"对象"(object)就是"基本构造单元"(building block)。开发者可以通过对象来存储并传递数据。在对象之间传递数据并执行任务的过程就叫做"消息传递"(Messaging)。当程序运行起来以后,为其提供相关支持的代码叫做"Objective-C运行期环境"(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。即我们写的oc代码,它在运行的时候是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的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一些常见的用法,关于详细的原理介绍推荐阅读以下博客:
Objective-C runtime的常见应用的更多相关文章
- 《ArcGIS Runtime SDK for Android开发笔记》——(11)、ArcGIS Runtime SDK常见空间数据加载
ArcGIS Runtime SDK for Android 支持多种类型空间数据源.每一种都提供了相应的图层来直接加载,图层Layer是空间数据的载体,其主要继承关系及类型说明如下图所示: 转载请注 ...
- Objective C Runtime 开发介绍
简介 Objective c 语言尽可能的把决定从编译推迟到链接到运行时.只要可能,它就会动态的处理事情.这就意味着它不仅仅需要一个编译器,也需要一个运行时系统来执行变异好的代码.运行时系统就好像是O ...
- 刨根问底Objective-C Runtime(4)- 成员变量与属性
http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...
- Objective-C Runtime(一)预备知识
很早就知道了Objective-C Runtime这个概念,「Objective-C奇技淫巧」「iOS黑魔法」各种看起来很屌的主题中总会有它的身影:但一直没有深入去学习,一来觉得目前在实际项目中还没有 ...
- iOS 常见知识点(三):Lock
iOS 常见知识点(一):Runtime iOS 常见知识点(二):RunLoop 锁是最常用的同步工具.一段代码段在同一个时间只能允许被有限个线程访问,比如一个线程 A 进入需要保护代码之前添加简单 ...
- iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象
1.前言 上一篇已经介绍了App Delegate.View Controller的基本概念,除此之外,分别利用storyboard和纯代码创建了第一个Xcode的工程,并对不同方式搭建项目进行了比较 ...
- 《ArcGIS Runtime SDK for Android开发笔记》
开发笔记之基础教程 ArcGIS Runtime SDK for Android 各版本下载地址 <ArcGIS Runtime SDK for Android开发笔记>——(1).And ...
- Runtime的相关知识
Runtime是近年来面试遇到的一个高频方向,也是我们平时开发中或多或少接触的一个领域,那么什么是runtime呢?它又可以用来做什么呢? 什么是Runtime?平时项目中有用过么? OC是一门动态性 ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
随机推荐
- Linux 开机时网络自动连接
简单版本: cd /etc/sysconfig/network-scripts/ vi ifcfg-enoXXX 输入:reboot重启 或者输入:service network restart ...
- MySQL数据库和InnoDB存储引擎文件
参数文件 当MySQL示例启动时,数据库会先去读一个配置参数文件,用来寻找数据库的各种文件所在位置以及指定某些初始化参数,这些参数通常定义了某种内存结构有多大等.在默认情况下,MySQL实例会按照一定 ...
- [C#] 简单的 Helper 封装 -- CookieHelper
using System; using System.Web; namespace ConsoleApplication5 { /// <summary> /// Cookie 助手 // ...
- Win.ini和注册表的读取写入
最近在做打包的工作,应用程序的配置信息可以放在注册表文件中,但是在以前的16位操作系统下,配置信息放在Win.ini文件中.下面介绍一下Win.ini文件的读写方法和注册表的编程. 先介绍下Win.i ...
- PHP获取上个月最后一天的一个容易忽略的问题
正常来说,PHP是有一个很方便的函数可以获取上个月时间的 strtotime (PHP 4, PHP 5, PHP 7) strtotime - 将任何英文文本的日期时间描述解析为 Unix 时间戳 ...
- css常用hack
原文地址:css常用hack 突然想起今天早上在CNZZ看到的统计数据,使用IE6.7的用户比例还真多,看到之后我的心都碎了.微软都放弃了为毛还有这么多人不死心? 所以说,IE下的兼容还是得做的. – ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- C#——传值参数(2)
//我的C#是跟着猛哥(刘铁猛)(算是我的正式老师)<C#语言入门详解>学习的,微信上猛哥也给我讲解了一些不懂得地方,对于我来说简直是一笔巨额财富,难得良师! 这次与大家共同学习C#中的 ...
- bzoj1723--前缀和(水题)
题目大意: 你难以想象贝茜看到一只妖精在牧场出现时是多么的惊讶.她不是傻瓜,立即猛扑过去,用她那灵活的牛蹄抓住了那只妖精. "你可以许一个愿望,傻大个儿!"妖精说. ...
- 记一次.NET代码重构
好久没写代码了,终于好不容易接到了开发任务,一看时间还挺充足的,我就慢慢整吧,若是遇上赶进度,基本上直接是功能优先,完全不考虑设计.你可以认为我完全没有追求,当身后有鞭子使劲赶的时候,神马设计都是浮云 ...