iOS runtime的应用实例
一直想弄明白runtime是怎么回事,因为面试的时候这是一道必备问题,但是平时用的机会真的少之又少,我一度以为runtime只是用来装13的利器,没什么卵用。但是随着学习的增多,发现runtime真的很有用,但也没那么神秘。我相信看了我这篇博客,您对runtime肯定会有自己的理解。
先说说OC与C的对比:
1.OC是对OC的面向对象的封装,OC中的对象只是C中指向结构体的指针。
2.OC的方法,本质上就是C语言中的函数,OC中的任意一个方法,在runtime中都会有一个与之对应的函数。eg:[objc sendMessage:@"I am back"]; -> objc_msg(self,@selector(sendMessage),"I am back");所以说在OC中对象调用方法,到运行的时候,都会变成向对象发送消息,这就是runtime中最著名的消息机制。
3.既然本质都是函数,那是不是和C语言的函数没有区别呢?绝对不是。
(1)C语言只能调用实现过的函数,只声明了是不行的,编译是不能通过的。
(2)OC无所谓,只要声明了就能调用,即时你没声明都能调用,编译阶段都不会报错,只会报警告。
- (id)performSelector:(SEL)aSelector;
这样据说是保证了编程的灵活性,反正大家都这么说,但是我觉得这就是不够严谨,因为真要是需要这个方法执行了,程序就得崩溃,在编译的时候就能解决的问题,为什么要等到程序崩溃再修改代码呢,有点浪费时间啊。
下面列举了几个runtime的应用实例,先用起来,用的多了,自然就理解了。
1.runtime的常用方法
runtime可以动态获取一个对象的成员变量、属性、方法、遵守的协议。
#import <Foundation/Foundation.h>
#import "ProtocolTest1.h"
#import "ProtocolTest2.h"
@interface Person : NSObject <ProtocolTest1,ProtocolTest2>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
- (NSString *)getName;
- (NSString *)getAge;
+ (void)classMethodTest;
@end
#import "Person.h"
@implementation Person
- (NSString *)getName {
return @"I am Tomcat";
}
- (NSString *)getAge {
return @"I will be 18 years old forever";
}
+ (void)classMethodTest {
NSLog(@"This is a class method");
}
- (NSString *)description {
return [NSString stringWithFormat:@"name=%@,age=%@",_name,_age];
}
@end
先看看我们的小白鼠Person类,我们就拿他做实验,动态获取他的成员变量、属性、方法、遵守的协议。
#import "FirstViewController.h"
#import <objc/runtime.h>
#import "Person.h"
@interface FirstViewController ()
@end
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *tomcat = [Person new];
unsigned int count;
NSLog(@"\n1.获得属性列表");
// 1.get describes of all properties (获得属性列表)
objc_property_t *propertyList = class_copyPropertyList([tomcat class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
printf("property = %s\n",propertyName);
}
// 2.get describes of all methods (获得方法列表)
NSLog(@"\n\n2.获得方法列表");
Method *methodList = class_copyMethodList([tomcat class], &count);
for (unsigned int i = 0; i < count; i++) {
SEL methodName = method_getName(methodList[i]);
NSLog(@"methodName = %@",NSStringFromSelector(methodName));
}
// 3.get describes of all variables (获得成员变量列表)
NSLog(@"\n\n3.获得成员变量列表");
Ivar *ivarList = class_copyIvarList([tomcat class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *ivarNmae = ivar_getName(ivarList[i]);
printf("ivarNmae = %s\n",ivarNmae);
// 动态变量控制
object_setIvar(tomcat, ivarList[i], @"哈哈,你被我改了");
}
NSLog(@"动态变量控制: name = %@",tomcat.name);
//4.get describes of all protocols adopted by a class (获得当前对象遵守的协议列表)
NSLog(@"\n\n4.获得协议列表");
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([tomcat class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *protocolNmae = protocol_getName(protocolList[i]);
printf("protocolNmae = %s\n",protocolNmae);
}
注意:我们要使用runtime库,首先要 #import <objc/runtime.h>。
2.拦截并替换方法
想要拦截和替换方法,首先要找到方法,根据什么找呢?方法名。
//5. 通过方法名获得类方法
Class personClass = object_getClass([Person class]);
SEL classSel = @selector(classMethodTest);
Method classMethod = class_getInstanceMethod(personClass, classSel);
//6. 通过方法名获得实例方法
SEL objSel1 = @selector(getName);
Method objMethod1 = class_getInstanceMethod([tomcat class], objSel1);
SEL objSe2 = @selector(getAge);
Method objMethod2 = class_getInstanceMethod([tomcat class], objSe2);
我们还可以交换这两个方法的实现
//7. 交换两个方法的实现
NSLog(@"\n\n交换两个方法的实现");
NSLog(@"交换之前 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);
method_exchangeImplementations(objMethod1, objMethod2);
NSLog(@"交换之后 --- getName = %@,getAge = %@", [tomcat getName],[tomcat getAge]);

拦截并替换方法,多用于给系统方法添加新的功能和修改第三方库。我们现在实现一个功能,就是给计算按钮的点击计数。
#import "UIButton+Count.h"
#import <objc/runtime.h>
#import "Tool.h"
@implementation UIButton (Count)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [self class];
// 1.原来的方法
SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
// 2.现在的方法
SEL cusSel = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSel);
// 3.给原来的方法添加实现,防止原来的方法没有实现,只有声明崩溃
BOOL addSuc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSuc) {
// 添加成功,用现在的方法的实现替换原来方法的实现
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 shareInstance] countClicks];
[super sendAction:action to:target forEvent:event];
}
@end
Tool.h
#import <Foundation/Foundation.h>
@interface Tool : NSObject
@property (nonatomic, assign) NSInteger count;
+ (instancetype)shareInstance;
- (void)countClicks;
@end
Tool.m
#import "Tool.m"
@implementation Tool
static id _instance;
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[Tool alloc] init];
});
return _instance;
}
- (void)countClicks {
_count += 1;
NSLog(@"您点击了%ld次",_count);
}
@end
3.实现NSCoding自动归档解档
NSCoding用于存储模型对象,必须实现代理NSCoding。
#import "Student.h"
#import <objc/runtime.h>
@implementation Student
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 如果不用runtime
// [aCoder encodeObject:self.name forKey: @"name"];
// [aCoder encodeObject:self.stuID forKey: @"stuID"];
// [aCoder encodeObject:self.score forKey: @"score"];
// [aCoder encodeObject:self.name forKey: @"myFriend"];
unsigned int count = 0;
// 获取变量列表
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
// 获取变量名
const char *name = ivar_getName(ivars[i]);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
// 用变量名归档变量
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
// 如果不用runtime
// self.name = [aDecoder decodeObjectForKey:@"name"];
// self.stuID = [aDecoder decodeObjectForKey:@"stuID"];
// self.score = [aDecoder decodeObjectForKey:@"score"];
// self.myFriend = [aDecoder decodeObjectForKey:@"myFriend"];
unsigned int count = 0;
// 获取变量列表
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
// 获取变量名
const char *name = ivar_getName(ivars[i]);
NSString *key = [NSString stringWithUTF8String:name];
// 用变量名解档变量
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"name=%@\nstuID=%@\nscore=%@\nmyFriend:%@",_name,_stuID,_score,_myFriend];
}
@end
如果模型属性少无所谓,如果多的话,最好用runtime。有100个属性,你不可能写100次解档归档啊。
4.实现字典转模型的自动转换
其实这个用系统方法就很好了,这次我用runtime实现一下,估计系统方法也是这个逻辑。
#import "NSObject+Model.h"
#import <objc/runtime.h>
@implementation NSObject (Model)
+ (instancetype)allocWithDic: (NSDictionary *)dic {
id objc = [[self alloc] init];
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSString *key = [ivarName substringFromIndex:1];
id value = dic[key];
NSLog(@"type = %@",ivarType);
// 把模型转化的字典再转换成模型
// 先判断value类型,value类型是自字典,但是ivarType又不是NSDictionary,说明value实际上是一个模型转化的字典
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
// @"Person" -> "Person"
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// "Person" -> Person
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 生成模型
Class modelClass = NSClassFromString(ivarType);
if (modelClass) {
// 给模型里的属性赋值
value = [modelClass allocWithDic:value];
}
}
if (value) {
[objc setValue:value forKey:key];
}else {
NSLog(@"没找到value");
}
}
return objc;
}
@end
rumtime库是一个非常强大的,我列举的这几个用法是比较常用和基础的。例外runtime是开源的,不过看起来应该很难,基本上是用c语言和汇编写的,现在懂汇编的应该很少。本文完整案例已经上传到Github,欢迎下载。
iOS runtime的应用实例的更多相关文章
- ios runtime的相关知识
一.iOS runtime原理 对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道r ...
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...
- iOS --runtime理解
iOS~runtime理解 Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下,一为 查阅方便二为 或许能给 ...
- iOS之微博UI实例--拟物化设计(成功了90%)
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- ios runtime swizzle
ios runtime swizzle @implementation NSObject(Extension) + (void)swizzleClassMethod:(Class)class orig ...
- 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进行以下操作 ...
随机推荐
- 2)Javascript设计模式:Singleton模式
Singleton模式 var User = (function() { var instance; function _User(){} _User.prototype.say = function ...
- linux 5个查找命令
1. find find是最常见和最强大的查找命令,你可以用它找到任何你想找的文件. find的使用格式如下: $ find <指定目录> <指定条件> <指定动作> ...
- centos 6.5中安装hadoop2.2
1.配置集群机器之间ssh免密码登录 (1) ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 将id_dsa.pub 公钥 加入授权的key中去 这条命令的功能是把公 ...
- Grunt-几个常用的任务配置,加载,执行的写法
http://www.gruntjs.net/sample-gruntfile Gruntfile 实例 下面就针对一个 Gruntfile 案例做简单分析,也可以作为一个实例使用: module.e ...
- Javascript正则表达式完全学习手册
正则表达式看起来很吓人,不容易让人亲近,但它的确很好用,可以很大程度上提高你的开发效率.本文从零开始介绍了应如何学习正则表达式.文中范例主要为JavaScript. 正则表达式可以很恐怖,真得很恐怖. ...
- 去掉删除discuz x3.2 的-Powered by Discuz!
如图discuz论坛 网站标题栏的尾巴powered by discuz!是不是很想删除呢,特别是为什么会剩下短线呢?下面就叫你如何准确删除或者修改. 工具/原料 8UFTP(使用自己熟悉的网站文件上 ...
- delphi中获得进程列表或想要的进程(枚举进程、遍历进程)
一个常见的编程任务是枚举所有运行的"应用程序".Windows 任务管理器就是一个很好的例子.它用两种方式列出"应用程序".任务管理器的第一个选项卡列出桌面上的 ...
- MySQL timestamp NOT NULL插入NULL的问题
explicit_defaults_for_timestamp MySQL 5.6版本引入 explicit_defaults_for_timestamp 来控制对timestamp NULL值的处理 ...
- 大数据时代的杀手锏----Tachyon
一.Tachyon系统的简介 Tachyon是一个分布式内存文件系统,可以在集群里以访问内存的速度来访问存在tachyon里的文件.把 Tachyon是架构在最底层的分布式文件存储和上层的各种计算框架 ...
- #图# #SPFA# #Tarjan# ----- BZOJ1179
SPFA算法 SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法. 判负环(在差分约束系统中会得以体现).如果某个点进入队列的次数超过N ...