什么是runtime
runtime即运行时,它是一个库.这个库是c、c++、汇编语言编写的,提供的API基本都是c语言的.正是由于这个库的存在,才使得oc具备了面向对象的能力,也使得oc成为了一门动态语言.对于C语言,函数的调用在编译的时候会决定调用哪个函数.对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用.
和运行时系统的交互
Objective-C 程序有三种途径和运行时系统交互:
1.通过 Objective-C 源代码;
2.通过 Foundation 框架中类 NSObject 的方法;
3.通过直接调用运行时系统的函数@selector
runtime本质
runtime本质就是对象发送消息(objc_msgsend).也就是说任何方法的调用,都是转化为消息机制.
Person *p = [[Person alloc] init];
[p test];
上面方法的调用 [p test],会被编辑器转弯runtime库中的objc_megsend调用的方式来执行,即:
objc_msgSend(p,sel_registerName("test"))
第一步:对象通过isa指针找到它所继承的类class;
第二步:在class的method_list中查找对应的方法;
第三步:如果未查找到当前方法,会向superclass类中查找,直到找到当前调用的方法
如果每次调用方法都需要遍历,系统消耗比较大,因此需要对常用的方法做缓存操作,每次查找先找缓存,就可以避免大量的无效操作。
每一个对象都存在一个isa指针,指向对象的类,类也是一个对象也存在一个isa指针指向元类,元类指向根元类,根元类指向自己。类中保存所有的实列方法,元类保存了所有类方法。方法查找过程:
runtime消息转发
使用对象调用一个方法时,系统会查看这个对象是否能够接受到这个消息,如果接受不到一般情况会崩溃,在崩溃之前会依次调用一下几个方法
1、解决实列方法和类方法
+(BOOL)resolveInstanceMethod:(SEL)sel;
+(BOOL)resolveClassMethod:(SEL)sel;
2、消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector;
3、消息签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
方法的执行流程
因此在崩溃之前,我们可以在以上几个方法中处理解决崩溃问题
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod:%@",NSStringFromSelector(sel));
if (![self respondsToSelector:sel]) {
class_addMethod(self.class, sel, (IMP)noMethod, "");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void noMethod(Class cls,SEL _cmd){
NSLog(@"我是一个没实现的方法");
}
runtime实际应用
1.方法交换
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//原本调用viewWillAppear
Method m1 = class_getInstanceMethod(self, @selector(viewWillAppear:));
//现在调用新的方法
Method m2 = class_getInstanceMethod(self, @selector(new_viewWillAppear:));
//runtime的2个方法交换
method_exchangeImplementations(m1, m2);
});
}
- (void)new_viewWillAppear:(BOOL) animated {
NSLog(@"%s", __func__);
//这个方法其实就是调用viewWillAppear
[self new_viewWillAppear:animated];
}
2.动态获取对象的属性,私有变量,修改属性值
/ /获取当前类的所有方法名称和个数
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList([self class], &methodCount);
NSLog(@"%d",methodCount);
NSMutableArray *methodArray = [NSMutableArray arrayWithCapacity:methodCount];
for (int i=0; i<methodCount; i++) {
Method temp = methodList[i];
SEL name = method_getName(temp);
const char *name_s = sel_getName(name);
NSLog(@"name:%s",name_s);
}
打印结果:
8
name:person1
name:person2
name:setPerson1:
name:setPerson2:
name:.cxx_destruct
name:observeValueForKeyPath:ofObject:change:context:
name:touchesBegan:withEvent:
name:viewDidLoad
获取属性并修改属性值
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self.class, &count);
for (int i=0; i<count; i++) {
Ivar ivar = ivarList[i];
const char *ivarAddresss = ivar_getName(ivar);
const char *ivarType = ivar_getTypeEncoding(ivar);
NSString *address = [NSString stringWithUTF8String:ivarAddresss];
NSLog(@"%d :ivarAddresss:%s ivarType:%s",i,ivarAddresss,ivarType);
//修改属性值
if ([address isEqualToString:@"var_name"]) {
object_setIvar(self, ivar, @"123456");
}
}
NSLog(@"var_name:%@",var_name);
打印结果:
0 :ivarAddresss:var_name ivarType:@"NSString"
var_name:123456
3.给分类添加属性
1、给对象关联一个属性
objc_setAssociatedObject(array, &array_key, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
2、通过绑定key获取关联对象
NSDictionary *di = objc_getAssociatedObject(array, &array_key);
3、移除关联对象
objc_setAssociatedObject(array, &array_key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
4、移除所有关联对象
objc_removeAssociatedObjects(array);
eg:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIButton (test)
@property (nonatomic,strong) NSString *addrees;
@end
#import "UIButton+test.h"
#import <objc/runtime.h>
const NSString *button_category_addrees = @"button_category_addrees";
@implementation UIButton (test)
-(void)setAddrees:(NSString *)ids{
objc_setAssociatedObject(self, &button_category_addrees, ids, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)addrees{
return objc_getAssociatedObject(self, &button_category_addrees);
}
@end
代码调用:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake((self.view.frame.size.width-100)/2, 200, 100, 40);
button.backgroundColor = [UIColor redColor];
button.addrees = @"陕西西安市雁塔区科技路";
[button setTitle:@"点击" forState:UIControlStateNormal];
[button addTarget:self action:@selector(btnEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
-(void)btnEvent:(UIButton *)btn{
NSLog(@"打印UIButton添加address属性值是:%@",btn.addrees);
}
打印结果:
打印UIButton添加address属性值是:陕西西安市雁塔区科技路