Objective-C:运行时runtime
通知在哪一个线程发的,那么对通知事件的处理就在同一个线程中进行;
1> runtime是一套底层的C语言API(包含很多强大实用的C语言数据类型、C语言函数)
2> 实际上,平时我们编写的OC代码,底层都是基于runtime实现的
* 也就是说,平时我们编写的OC代码,最终都是转成了底层的runtime代码(C语言代码)
runtime有啥用?
1> 能动态产生一个类、一个成员变量、一个方法
2> 能动态修改一个类、一个成员变量、一个方法
3> 能动态删除一个类、一个成员变量、一个方法
常见的函数、头文件
#import <objc/runtime.h> : 成员变量、类、方法
Ivar * class_copyIvarList : 获得某个类内部的所有成员变量
Method * class_copyMethodList : 获得某个类内部的所有方法
Method class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)
Method class_getClassMethod : 获得某个类方法(加号+开头)
method_exchangeImplementations : 交换2个方法的具体实现
#import <objc/message.h> : 消息机制
objc_msgSend(….)
什么是iOS Swizzle? 利用运行时函数交换2个方法的实现
具体的距离如下:
1、测试运行时的消息机制:要在测试类头文件中导入<objc/message.h>
Person类:
#import <UIKit/UIKit.h> @interface Person : NSObject //<NSCoding>
@property (copy,nonatomic)NSString *name;
@property (assign,nonatomic)NSInteger age;
@property (assign,nonatomic)CGFloat height;
-(void)run;
@end #import "Person.h"
#import <objc/runtime.h> @implementation Person
-(void)run
{
NSLog(@"run-----");
} @end
测试类:
//测试运行时的消息机制
-(void)testMessage
{
//<objc/message.h>
Person *p = [[Person alloc]init]; p.age = ;
objc_msgSend(p, @selector(setAge:),); // <====> [p setAge:20]
NSLog(@"%zi",p.age); objc_msgSend(p, @selector(age)); //<======> [p age] [p run];
objc_msgSend(p, @selector(run)); // <====> [p run]
}
测试结果如下:
-- ::27.397 Runtime-运行时[:]
-- ::27.398 Runtime-运行时[:] eat------
-- ::27.398 Runtime-运行时[:] eat------
-- ::27.398 Runtime-运行时[:] eat------
-- ::27.399 Runtime-运行时[:] eat------
2、获取运行时的成员属性
//获取运行时的的成员属性
-(void)testRuntimeIvar
{
//<objc/runtime.h>
//Ivar:成员变量
unsigned int count = ;
Ivar *ivars = class_copyIvarList([Person class], &count);
NSLog(@"%d",count); //取得成员变量的数量
for (int i=; i<count; i++)
{
//取得i位置的成员变量
Ivar ivar = ivars[i]; //const char *ivar_getName(Ivar v) 获取属性名称
const char *ivarName = ivar_getName(ivar); //const char *ivar_getTypeEncoding(Ivar v) 获取成员变量的类型
const char *ivarType = ivar_getTypeEncoding(ivar); NSLog(@"%d %s %s",i,ivarName,ivarType);
}
}
测试结果如下:
-- ::01.013 Runtime-运行时[:]
-- ::01.014 Runtime-运行时[:] _name @"NSString"
-- ::01.014 Runtime-运行时[:] _age q
-- ::01.014 Runtime-运行时[:] _height d
扩展:利用这个上面获取属性的这个方法,可以很轻松的实现对大量的类的属性进行归档和解归档
//归档
-(void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = ;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i=; i<count; i++)
{
Ivar ivar = ivars[i];
const char *ivarName = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:ivarName]; [encoder encodeObject:[self valueForKey:key] forKey:key];
}
} //解归档
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self)
{
unsigned int count = ;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i=; i<count; i++)
{
Ivar ivar = ivars[i];
const char *ivarName = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:ivarName];
key = [decoder decodeObjectForKey:key];
}
}
return self;
}
3、获得运行时的成员方法
//获取运行时的的成员方法
-(void)testRuntimeMethod
{
//<objc/runtime.h>
//Method: 成员方法
unsigned int count = ;
Method *methods = class_copyMethodList([Person class], &count);
NSLog(@"%d",count); //取得成员方法的数量
for (int i=; i<count; i++)
{
//取得i位置的成员方法
Method method = methods[i]; //SEL method_getName(Method m) 获取方法名称
SEL sel = method_getName(method);
const char *selName = sel_getName(sel);
NSLog(@"%s",selName); //const char *method_getTypeEncoding(Method m)
//Returns a string describing a method's parameter and return types
const char *methodType = method_getTypeEncoding(method);
NSLog(@"%s",methodType); //char *method_copyReturnType(Method m) 获取成员方法的返回值类型
char *method_return_type = method_copyReturnType(method);
NSLog(@"%s",method_return_type); //unsigned int method_getNumberOfArguments(Method m) 获取成员方法的参数个数
count = method_getNumberOfArguments(method);
NSLog(@"%zi",count);
}
}
测试结果如下:
-- ::33.081 Runtime-运行时[:]
-- ::33.081 Runtime-运行时[:] setAge:
-- ::33.081 Runtime-运行时[:] v24@:8q16
-- ::33.082 Runtime-运行时[:] v
-- ::33.082 Runtime-运行时[:]
-- ::33.082 Runtime-运行时[:] age
-- ::33.082 Runtime-运行时[:] q16@:
-- ::33.082 Runtime-运行时[:] q
-- ::33.082 Runtime-运行时[:]
4、获得运行时的协议
-(void)testRuntimeProtocol
{
//<objc/runtime.h>
//Protocol:协议
//unsigned int count = 0;
//Protocol * __unsafe_unretained *protocol = class_copyProtocolList([Person class], &count);
//.........................
}
5、在运行时中动态添加方法、属性、协议等
-(void)testRuntimeAdd
{
//动态添加方法
//BOOL class_addMethod(Class cls, SEL name, IMP imp,const char *types) //动态替换方法
//IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types) //动态添加成员变量
//BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types) //动态添加协议
//BOOL class_addProtocol(Class cls, Protocol *protocol) //动态添加属性
//BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) //动态替换属性
//void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) //........................
}
6、在运行时中动态的交换两个实现方法
-(void)testRuntimeChangedMethod
{
//什么是iOS Swizzle? 利用运行时函数交换2个方法的实现 //class_getClassMethod(__unsafe_unretained Class cls, SEL name) //类方法
//class_getInstanceMethod(__unsafe_unretained Class cls, SEL name) //实例方法
//method_exchangeImplementations(Method m1, Method m2) //动态交换实现方法
}
下面我就来验证动态的交换两个实现方法:
例子一:动态交换类方法和实例方法的实现方法
<1>在Person类中声明和定义一个run方法
#import <UIKit/UIKit.h> @interface Person : NSObject
-(void)run;
@end #import "Person.h"
#import <objc/runtime.h> @implementation Person
-(void)run
{
NSLog(@"run-----");
}
<2>在Person类扩展中定义eat方法和加载内存时交换这两个方法的实现
#import "Person.h"
#import <objc/runtime.h> @implementation Person (Extension) //程序一运行就会加载
+(void)load
{
//获取实例方法和类方法
Method classMethod = class_getClassMethod([Person class], @selector(eat));
Method instanceMethod = class_getInstanceMethod([Person class], @selector(run)); //交换实例实现方法和类实现方法
method_exchangeImplementations(classMethod, instanceMethod);
} +(void)eat
{
NSLog(@"eat------");
Person *p = [[Person alloc]init]; //死循环
[p run];
}
@end
<3>测试如下:
Person *p = [[Person alloc]init];
[p run];
当Person类对象调用run方法时出现死循环:我只给出一部分结果
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.268 Runtime-运行时[:] eat------
-- ::22.269 Runtime-运行时[:] eat------
-- ::22.269 Runtime-运行时[:] eat------
解释原因:因为Person类一存在的时候,就会调用+(void)load方法,将run实例方法和eat类方法进行了交换,即实际上[p run]方法被没有执行,而是执行了[Person eat]方法,在[Person eat]中第一次输出NSLog(@"eat-------")后,紧接着又创建了一个新的Person对象,这个对象也调用了[p run]方法,就又调用了[Person eat]类方法,又一次输出
NSLog(@"eat-------"),一直如此循环下去...........
例子二:给OC内置的方法做手脚,用自定义的方法交换实现方法(以数组和可变数组为例)
<1>给NSObject、NSArray、NSMutableArray都创建扩展类。
在NSObejct扩展中创建两个类方法,用来交换实例方法和类方法
在NSArray扩展中创建两个方法,一个+(void)load方法实现交换,另一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:
和NSMutableArray创建三个方法,一个+(void)load方法实现交换,一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:,还有一个用来覆盖OC内置的方法addObject:
如下:
#import <UIKit/UIKit.h>
#import <objc/runtime.h> @implementation NSObject(Extension)
+(void)swizlleClassMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
//获取实例方法
Method classMethod1 = class_getClassMethod(class, originMethod);
Method classMethod2 = class_getClassMethod(class, otherMethod); //交换实例实现方法
method_exchangeImplementations(classMethod1, classMethod2);
} +(void)swizlleInstanceMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
//获取实例方法
Method instanceMethod1 = class_getInstanceMethod(class, originMethod);
Method instanceMethod2 = class_getInstanceMethod(class, otherMethod); //交换实例实现方法
method_exchangeImplementations(instanceMethod1, instanceMethod2);
}
@end @implementation NSArray(Extension) //程序一运行就会加载
+(void)load
{
[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)];
}
-(id)Test_objectAtIndex:(NSUInteger)index
{
if (index < self.count)
{
//如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
return [self Test_objectAtIndex:index];
}
else
{
//超界,返回空值
return nil;
}
} @end @implementation NSMutableArray(Extension) //程序一运行就会加载
+(void)load
{
//获取实例方法
[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(addObject:) otherMethod:@selector(Test_addObject:)]; //交换实例实现方法
[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)]; } -(void)Test_addObject:(id)object
{
if (object != nil)
{
//如果对象不为空,就调用交换后的系统的addObject:方法添加对象到可变数组中
[self Test_addObject:object];
}
} -(id)Test_objectAtIndex:(NSUInteger)index
{
if (index < self.count)
{
//如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
return [self Test_objectAtIndex:index];
}
else
{
//超界,返回空值
return nil;
}
}
@end
<2>测试:
声明数组:
@interface ViewController ()
@property (strong,nonatomic)NSMutableArray *names;
@property (strong,nonatomic)NSArray *books;
@end
NSArray:
self.books = @[@"水浒传",@"西游记"];
NSLog(@"%@",self.books[]); //[self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1]
NSLog(@"%@",self.books[]); //[self.books objectAtIndex:4] --> [self.books Test_objectAtIndex:4]
测试结果:
-- ::01.436 Runtime-运行时[:] 西游记
-- ::01.437 Runtime-运行时[:] (null)
解释原因:
因为程序一运行,就调用了+(void)load中的[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)]方法,
所以交换后就成了这种情况: self.books[1]---> [self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1],
明显的,1小于数组个数2,调用[self.books Test_objectAtIndex:1]-->[self.books objectAtIndex:1],返回@"西游记";
同理,由于self.books[4]的索引4大于数组个数2,所以返回null.
NSMutableArray:
[self.names addObject:@"jack"];
[self.names addObject:nil];
[self.names addObject:nil];
[self.names addObject:@"rose"];
[self.names addObject:@"jim"];
NSLog(@"%@",self.names[]);
NSLog(@"%@",self.names);
测试结果:
-- ::45.785 Runtime-运行时[:] (null)
-- ::45.786 Runtime-运行时[:] (
jack,
rose,
jim
)
分析原理同上,这里说一点,明显的如果我们没有实现方法的交换,使用[self.name addobject:nil],程序肯定是会崩掉的,OC中是不允许向可变数组中添加空对象的,但是我们在这个方法上做一些手脚,换成自定义的方法后,可以消除这个bug,输出自己想要的结果;
最后还有一个知识,我们都知道,OC的分类中只能用来添加方法,是不能添加属性的。
但是有了运行时这个概念,动态的给分类添加属性也就不是事。
演示截图如下:
1.给分类添加属性:

2.对象分类属性进行关联:

发现编译一下,不报错,okay,成了。
Objective-C:运行时runtime的更多相关文章
- Objective C运行时(runtime)
#import <objc/runtime.h> void setBeingRemoved(id __self, SEL _cmd) { NSLog(@"------------ ...
- iOS运行时Runtime浅析
运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...
- Deep Learning部署TVM Golang运行时Runtime
Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...
- CUDA运行时 Runtime(一)
CUDA运行时 Runtime(一) 一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...
- CUDA运行时 Runtime(四)
CUDA运行时 Runtime(四) 一. 图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...
- CUDA运行时 Runtime(三)
CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...
- CUDA运行时 Runtime(二)
CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...
- “ compiler-rt”运行时runtime库
" compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...
- Objective C运行时(runtime)技术的几个要点总结
前言: Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表.属性列表.变量列表,修改方法.属性,增加方法,属性等等,本文对相 ...
- Objective C运行时(runtime)技术总结,好强大的runtime
前言: Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表.属性列表.变量列表,修改方法.属性,增加方法,属性等等,本文对相 ...
随机推荐
- Codeforces 950E Data Center Maintenance 强连通分量
题目链接 题意 有\(n\)个信息中心,每个信息中心都有自己的维护时间\((0\leq t\lt h)\),在这个时刻里面的信息不能被获得. 每个用户的数据都有两份备份,放在两个相异的信息中心(维护时 ...
- python学习笔记 IO 文件读写
读写文件是最常见的IO操作.python内置了读写文件的函数. 读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统完成的,现代操作系统不允许普通的程序直接对磁盘进行操作,所以, 读写 ...
- 冒泡排序的思想 python 冒泡排序、递归排序
冒泡排序的时间复杂度是O(N^2) 冒泡排序的思想: 每次比较两个相邻的元素, 如果他们的顺序错误就把他们交换位置 比如有五个数: 12, 35, 99, 18, 76, 从大到小排序, 对相邻的两位 ...
- SVN代码提交
SVN代码提交(转载) 原文链接:http://www.softown.cn/post/100.html 1.SVN代码提交 1) 原则 先更新再提交: SVN是为了多人协同开发而产生的,如果你在提交 ...
- [BZOJ4537][Hnoi2016]最小公倍数 奇怪的分块+可撤销并查集
4537: [Hnoi2016]最小公倍数 Time Limit: 40 Sec Memory Limit: 512 MBSubmit: 1474 Solved: 521[Submit][Stat ...
- CSS基本属性—文本属性和背景属性
一.CSS常用文本属性 [css中的颜色表示方式] 1.直接使用颜色的单词表示:red.green.blue 2.使用颜色的十六进制表示:#ff0000,#00ff00: 六位数,两两 ...
- 【转】学一点Git--20分钟git快速上手
看到一篇不错的Git的简单入门教程,转过来给自己留个底. 原文地址:http://www.cnblogs.com/shuidao/p/3535299.html 在Git如日中天的今天,不懂git都不好 ...
- 提取windows用户明文密码
前段时间mimikatz热传,主要是因为可以直接提取当前登录用户明文密码. 其实,有个更厉害的神器,无需那么多命令操作,一个命令搞定: C:\>wce -w WCE v1.3beta (Wind ...
- bufferknife框架的正确使用方式 -终于他么知道了
1.由于ButterKnife从7.x到8.x后,运行时和编译器被分开来,你还需要配置 apt ‘com.jakewharton:butterknife-compiler:8.1.0’,在projec ...
- Codeforces 189A. Cut Ribbon
题目链接:http://codeforces.com/problemset/problem/189/A 题意: 给你一个长度为 N 的布条, 再给你三个长度 a, b , c.你可以用剪刀去剪这些布条 ...