本文最后更新于:2021年12月22日 中午
一、基础概念 RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。 OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。 只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是:
1、通过 Objective-C 源代码;
2、通过 Foundation 框架的NSObject类定义的方法;
3、通过对 runtime 函数的直接调用。 大部分情况下你就只管写你的objectivec代码就行,runtime 系统自动在幕后辛勤劳作着。
二、runtime的具体实现 我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。每一个oc的方法,底层必然有一个与之对应的runtime方法。
当我们用OC写下这样一段代码[tableView cellForRowAtIndexPath:indexPath];
在编译时RunTime会将上述代码转化成[发送消息]objectivec_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);
三、常见方法 1、获取属性列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @interface People : NSObject @property (nonatomic , strong ) NSString *name;@property (nonatomic , assign ) NSUInteger age;@end @interface People () { NSString *aaa; }@property (nonatomic , strong ) NSString *fatherName;@end @implementation People { NSString *bbb; }@end
1 2 3 4 5 6 7 8 9 -(void )getAllProperty { unsigned int count = 0 ; objectivec_property_t *propertyList = class_copyPropertyList([People class ], &count); for (unsigned int i=0 ; i<count; i++) { const char *propertyName = property_getName(propertyList[i]); NSLog (@"property---->%@" , [NSString stringWithUTF8String:propertyName]); } }
打印结果:
结论:
1)、不管是在.h文件中定义的属性还是在.m文件中定义的属性,都可以通过获取属性列表方法来进行获取;
2)、成员变量不同于属性,不能通过该方法来获取;
3)、先输出的是.m文件中的属性,然后才是.h文件中的属性,并且是按照属性定义的先后顺序来保存。
2、获取方法列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @interface People : NSObject @property (nonatomic , strong ) NSString *name; -(void )iPrintName; +(void )cPrintName;@end @interface People () { NSString *aaa; }@property (nonatomic , strong ) NSString *fatherName;@end @implementation People { NSString *bbb; } +(void )cPrintName { } -(void )printAge { }@end
1 2 3 4 5 6 7 8 9 -(void )getAllMethod { unsigned int count = 0 ; Method *methodList = class_copyMethodList([People class ], &count); for (unsigned int i=0 ; i<count; i++) { Method method = methodList[i]; NSLog (@"method---->%@" , 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、获取成员变量列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @interface People : NSObject { NSString *cccc; }@property (nonatomic , strong ) NSString *name;@end @interface People () { NSString *aaa; }@property (nonatomic , strong ) NSString *fatherName;@end @implementation People { NSString *bbb; }@end
1 2 3 4 5 6 7 8 9 -(void)getAllIvar{ unsigned int count = 0 ; Ivar *ivarList = class _copyIvarList([People class ], &count ) ; for (unsigned int i=0 ; i<count; i++) { Ivar myIvar = ivarList[i ] ; const char *ivarName = ivar_getName(myIvar ) ; NSLog(@"Ivar---->%@" , [NSString stringWithUTF8String :ivarName ]) ; } }
打印结果: 结论: 1)、成员变量的保存是从.h文件开始,然后才是.m文件中的成员变量; 2)、用@property 定义的属性,会自动生成以_开头的成员变量,也是先保存.h文件生成的,再保存.m文件生成的。
4、获取协议列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @protocol PeopleDelegate <NSObject > -(void )people;@end @interface People : NSObject @property (nonatomic , strong ) NSString *name;@end @interface People ()@property (nonatomic , strong ) NSString *fatherName;@end @implementation People @end @interface ViewController ()<PeopleDelegate ,UITabBarDelegate ,UITableViewDataSource >@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self getProtocal]; }@end
1 2 3 4 5 6 7 8 9 10 -(void )getProtocal { unsigned int count = 0 ; __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class ], &count); for (unsigned int i = 0 ; i<count; i++) { Protocol *myProtocal = protocolList[i]; const char *protocolName = protocol_getName(myProtocal); NSLog (@"protocol---->%@" , [NSString stringWithUTF8String:protocolName]); } }
打印结果:
结论:
1)、只要声明遵循该协议,在引用的时候,就能获取到该类包含的协议列表,哪怕你没有指定代理的对象,也没有去实现协议的方法
5、获取类方法与实例方法以及方法交换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @interface People : NSObject @property (nonatomic , strong ) NSString *name; -(void )printInstanceMethod; +(void )printClassMethod;@end @interface People ()@property (nonatomic , strong ) NSString *fatherName;@end @implementation People -(void )printInstanceMethod{ NSLog (@"我是实例方法" ); } +(void )printClassMethod { NSLog (@"我是类方法" ); }@end
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -(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 来实现我们想要的效果。参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #import "Car+MyCar.h" #import <objectivec/runtime.h> 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语言代码可以替换成以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 - (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
调用:
1 2 3 4 5 -(void )addMethod { Car *c = [[Car alloc] init]; [c performSelector:@selector (drive) withObject:@"BMW" ]; }
解释: 在 Objective-C 中,正常的调用方法是通过消息机制(message)来实现的,那么如果类中没有找到发送的消息方法,系统就会进入找不到该方法的处理流程中,如果在这个流程中,我们加入我们所需要的新方法,就能实现运行过程中的动态添加了。这个流程或者说机制,就是 Objective-C 的 Message Forwarding 。 这个机制中所涉及的方法主要有两个:
1 2 + (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)这个判定就可见一斑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 +(void )load{ NSString *className = NSStringFromClass (self .class); NSLog (@"classname %@" , className); static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ 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); } }); }
Demo
资料 runtime详解 runtime奇技淫巧系列 OC最实用的runtime总结,面试、工作你看我就足够了!
五、联系方式 邮箱: xiebangyao_1994@163.com 相关账号: