一直想弄明白 runtime 是怎么回事,因为面试的时候这是一道必备问题,但是平时用的机会真的少之又少,我一度以为 runtime 只是用来装 13 的利器,没什么卵用。但是随着学习的增多,发现 runtime 真的很有用,但也没那么神秘。我相信看了我这篇博客,您对 runtime 肯定会有自己的理解。
先说说 OC 与 C 的对比:
1.OC 是对 OC 的面向对象的封装,OC 中的对象只是 C 中指向结构体的指针。
2.OC 的方法,本质上就是 C 语言中的函数,OC 中的任意一个方法,在 runtime 中都会有一个与之对应的函数。eg:
->
- [objc sendMessage:@"I am back"];
所以说在 OC 中对象调用方法,到运行的时候,都会变成向对象发送消息,这就是 runtime 中最著名的消息机制。 3. 既然本质都是函数,那是不是和 C 语言的函数没有区别呢?绝对不是。
- objc_msg(self,@selector(sendMessage),"I am back");
- - (id)performSelector:(SEL)aSelector;
这样据说是保证了编程的灵活性,反正大家都这么说,但是我觉得这就是不够严谨,因为真要是需要这个方法执行了,程序就得崩溃,在编译的时候就能解决的问题,为什么要等到程序崩溃再修改代码呢,有点浪费时间啊。
下面列举了几个 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>。
想要拦截和替换方法,首先要找到方法,根据什么找呢?方法名。
- //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
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 次解档归档啊。
其实这个用系统方法就很好了,这次我用 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 语言和汇编写的,现在懂汇编的应该很少。本文完整案例已经上传到,欢迎下载。
来源: http://www.cnblogs.com/knowledgesea/p/6232617.html