一、runtime简介
Objective-C是一种动态语言,所谓动态语言,是在程序执行时动态的确定变量类型,执行变量类型对应的方法的。因此,在Object-C中常用字符串映射类的技巧来动态创建类对象。因为OC的动态语言特性,我们可以通过一些手段,在程序运行时动态的更改对象的变量甚至方法,这就是我们所说的runtime机制。
- RunTime简称运行时。OC是动态语言,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会确定变量类型,根据函数的名称找到对应的函数来调用。 而C语言,函数的调用在编译的时候会决定调用哪个函数。
- 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错 ,而C语言调用未实现的函数就会报错。
二、runtime作用( 必须导入#import <objc/message.h> ;在build setting中搜objc_msg设置“NO”)
1.发送消息(最主要的运行时机制)
方法调用的本质,就是让对象发送消息。
objc_msgSend,只有对象才能发送消息,因此以objc开头.
消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现
Dog *dog = [[Dog alloc] init];
[dog bite]; // 对象方法
// 底层是实现,对象发送消息
objc_msgSend(dog, @selector(bite));
[Dog bite]; // 类方法方法
// 底层是实现,底层会自动把类名转换成类对象调用
objc_msgSend([Dog class], @selector(bite));
2.交换方法(黑魔法)
需求: 在保持原有的功能的情况下,给系统自带的方法扩展一些功能。
方案:
一: 继承系统的类,重写方法.
二: 使用runtime,交换方法.
:给imageNamed方法扩展功能,每次加载图片后打印“输出图片”。
@implementation UIImage (Extention)
+ (void)load {
// 交换方法
// 1. 获取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 2. 获取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 3. 交换方法地址,相当于交换实现方式
method_exchangeImplementations(imageWithName, imageName);
}
+ (UIImage *)imageWithName:(NSString *)icon {
// load方法中方法交换后,这里调用imageWithName,就相当于调用imageName(并不会发生死循环)
UIImage *image = [self imageWithName:icon];
NSLog(@"输出图片");
return image;
}
3.动态添加方法
需求:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
简单使用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [[Dog alloc] init];
[dog performSelector:@selector(eat)]; // 对象方法,直接使用[dog eat;似乎也可以
}
@end
@implementation Dog
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 动态添加eat方法
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
4.给分类添加属性
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
5.获取对象的所有属性
需求:修改.m文件中的私有属性
Dog *dog = [[Dog alloc] init];
//我们先声明一个unsigned int
//调用runtime的方法
//Ivar:方法返回的对象内容对象,这里将返回一个Ivar类型的指针
//class_copyIvarList方法可以捕获到类的所有变量,将变量的数量存在一个unsigned int的指针中
unsigned int count;
Ivar * memList = class_copyIvarList([Dog class], &count);
//进行遍历
for (int i=0; i< count ; i++) {
//通过移动指针进行遍历
Ivar var = memList[i];
//获取变量的名称
NSString *name = [NSString stringWithUTF8String:ivar_getName(var)];
//获取变量的类型
NSLog(@"%@",name);
}
object_setIvar(dog, memList[0], @"pp");
需求:调用.m文件中实现的方法
unsigned int count;
Method *memList = class_copyMethodList([Dog class], &count);
//遍历
for(int i=0; i<count; i++){
SEL name = method_getName(memList[i]);
NSString * method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"%@\\n",method);
}
SEL name = method_getName(memList[0]);
NSString *method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
[dog performSelector:name withObject:nil];
6.用我们的函数替换掉类中的函数(黑魔法)
这个太屌,可以将任何一个类的方法替换掉
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [[Dog alloc]init]; //替换之前的方法
class_replaceMethod([Dog class], @selector(bite), (IMP)logHAHA, "q");
[dog bite];
}
void logHAHA(){
NSLog(@"HAHA");}
网友评论