一:基础概念
RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是:
1、通过 Objective-C 源代码;
2、通过 Foundation 框架的NSObject类定义的方法;
3、通过对 runtime 函数的直接调用。
大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
二:runtime的具体实现
我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。
当我们用OC写下这样一段代码
[tableView cellForRowAtIndexPath:indexPath];
在编译时RunTime会将上述代码转化成[发送消息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);
三:常见方法
1、获取属性列表
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
@end
//获取所有属性
-(void)getAllProperty {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([People class], &count);
for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);
}
}
打印结果:
结论:
1)、不管是在.h文件中定义的属性还是在.m文件中定义的属性,都可以通过获取属性列表方法来进行获取;
2)、成员变量不同于属性,不能通过该方法来获取;
3)、先输出的是.m文件中的属性,然后才是.h文件中的属性,并且是按照属性定义的先后顺序来保存。
2、获取方法列表
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
-(void)iPrintName;
+(void)cPrintName;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
//-(void)printName {
//
//}
+(void)cPrintName {
}
-(void)printAge {
}
@end
//获取方法(不包括类方法)列表
-(void)getAllMethod {
unsigned int count = 0;
Method *methodList = class_copyMethodList([People class], &count);
for (unsigned int i=0; i%@", NSStringFromSelector(method_getName(method)));
}
}
结论:
1)、类方法不能通过这个函数去获取到;
2)、只有在.m文件中实现了的方法才能被获取到,在.h文件中定义了,但是.m中没有实现的并不能获取到;
3)、对于使用@property定义的属性,会自动生成setter和getter方法,同样能被这个方法获取到;
4)、.m实现中还隐藏了一个.cxx_destruct也就是oc中常见delloc方法;
5)、保存顺序是优先保存用户在.m文件中实现的,其次是.m属性自动生成的getter和setter方法,然后是隐藏的delloc方法,最后是.h属性自动生成的getter和setter方法。
3、获取成员变量列表
//People.h
@interface People : NSObject {
NSString *cccc;
}
@property (nonatomic, strong) NSString *name;
@end
//People.m
@interface People() {
NSString *aaa;
}
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People {
NSString *bbb;
}
@end
-(void)getAllIvar{
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([People class], &count);
for (unsigned int i=0; i%@", [NSString stringWithUTF8String:ivarName]);
}
}
打印结果:
结论:
1)、成员变量的保存是从.h文件开始,然后才是.m文件中的成员变量;
2)、用@property 定义的属性,会自动生成以_开头的成员变量,也是先保存.h文件生成的,再保存.m文件生成的。
4、获取协议列表
//People.h
@protocol PeopleDelegate
-(void)people;
@end
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
//@property (nonatomic, weak) id delegate;
@end
//People.m
@interface People()
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People
@end
//ViewController.m
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self getProtocal];
}
@end
//获取协议列表
-(void)getProtocal {
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); //这里变成了self
for (unsigned int i = 0; i%@", [NSString stringWithUTF8String:protocolName]);
}
}
打印结果:
结论:
1)、只要声明遵循该协议,在引用的时候,就能获取到该类包含的协议列表,哪怕你没有指定代理的对象,也没有去实现协议的方法
5、获取类方法与实例方法以及方法交换
//People.h
@interface People : NSObject
@property (nonatomic, strong) NSString *name;
-(void)printInstanceMethod;
+(void)printClassMethod;
@end
//People.m
@interface People()
@property (nonatomic, strong) NSString *fatherName;
@end
@implementation People
-(void)printInstanceMethod{
NSLog(@"我是实例方法");
}
+(void)printClassMethod {
NSLog(@"我是类方法");
}
@end
示例
-(void)getMethod {
People * p1 = [[People alloc] init];
Method m1 = class_getInstanceMethod([p1 class], @selector(printInstanceMethod));
Method m2 = class_getClassMethod([People class], @selector(printClassMethod));
NSLog(@"测试前:");
[p1 printInstanceMethod];
[People printClassMethod];
method_exchangeImplementations(m1, m2);
NSLog(@"测试后:");
[p1 printInstanceMethod];
[People printClassMethod];
}
打印结果
6、添加方法
当项目中,需要继承某一个类(subclass),但是父类中并没有提供我需要的调用方法,而我又不清楚父类中某些方法的具体实现;或者,我需要为这个类写一个分类(category),在这个分类中,我可能需要替换/新增某个方法(注意:不推荐在分类中重写方法,而且也无法通过 super 来获取所谓父类的方法)。大致在这两种情况下,我们可以通过 class_addMethod 来实现我们想要的效果。参考
//Car+MyCar.h
#import "Car+MyCar.h"
#import
void startEngine(id self, SEL _cmd, NSString *brand) {
NSLog(@"my %@ car starts the engine", brand);
}
@implementation Car (MyCar)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, (IMP)startEngine, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
不习惯C语言代码可以替换成以下代码:
- (void)startEngine:(NSString *)brand {
NSLog(@"my %@ car starts the engine", brand);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
调用:
-(void)addMethod {
Car *c = [[Car alloc] init];
[c performSelector:@selector(drive) withObject:@"BMW"];
}
解释:
在 Objective-C 中,正常的调用方法是通过消息机制(message)来实现的,那么如果类中没有找到发送的消息方法,系统就会进入找不到该方法的处理流程中,如果在这个流程中,我们加入我们所需要的新方法,就能实现运行过程中的动态添加了。这个流程或者说机制,就是 Objective-C 的 Message Forwarding 。
这个机制中所涉及的方法主要有两个:
+ (BOOL)resolveInstanceMethod:(SEL)sel;//实例方法
+ (BOOL)resolveClassMethod:(SEL)sel;//类方法
这个函数在 runtime 环境下,如果没有找到该方法的实现的话就会执行。第一行判断的是传入的 SEL 名称是否匹配,接着调用 class_addMethod 方法,传入相应的参数。其中第三个参数传入的是我们添加的 C 语言函数的实现,也就是说,第三个参数的名称要和添加的具体函数名称一致。第四个参数指的是函数的返回值以及参数内容。
结果:
装逼:
一开始我以为class_addMethod和class_replaceMethod就等同于method_exchangeImplementations,但是看了下面的代码才发现,其实这俩的适用条件是不一样的,当方法没有实现的时候才能使用class_addMethod和class_replaceMethod套装,但是当方法已存在的时候,就需要使用method_exchangeImplementations,这点从if (didAddMethod)这个判定就可见一斑。
+(void)load{
NSString *className = NSStringFromClass(self.class);
NSLog(@"classname %@", className);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要特别注意你替换的方法到底是哪个性质的方法
// When swizzling a Instance method, use the following:
// Class class = [self class];
// When swizzling a class method, use the following:
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
Runtime博大精深,看的越深入,感觉越懵 T_T,浅显的认知,欢迎大家提意见
Demo
资料
runtime详解
runtime奇技淫巧系列
OC最实用的runtime总结,面试、工作你看我就足够了!