Runtime使用C语言结构体表示对象,用C语言函数表示方法,这些C语言函数和结构体被Runtime封装后,我们就可以在程序中执行创建,检查,修改类和对象和他们的方法
runtime
1、是由C、C++、汇编写成的api2、OC运行时,装载到内存相对应的编译时,源代码翻译
OC SWIFT JAVA 高级语言,不被机器所识别,需要编译成响应的机器语言,二进制
Objective-c程序有三种途径和运行时系统交互
1、通过Objective-c源代码,如@selector()2、通过Foundation框架中NSObject的方法,如 iskindof3、通过调用运行时系统给我们提供的api接口,如objc_msgSend,objc_getClassOC对象本质是结构体
调用方法就是发送消息 objc_msgSend消息的组成:((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run")); 第一个参数p消息的接收者,第二个参数sel_registerName("run")方法编号imp 函数实现的指针,sel找到imp查看关系图
OC的Class其实是一个objc_class结构体的指针,下面是Class类的定义
typedef struct objc_class *Class;
查看objc/runtime.h中objc_class结构体的定义如下
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; //isa指针 #if !__OBJC2__ Classsuper_class OBJC2_UNAVAILABLE; // 父类const char *name OBJC2_UNAVAILABLE; // 类名long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0long info OBJC2_UNAVAILABLE; // 类信息long instance_size OBJC2_UNAVAILABLE; // 类占据的内存大小struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 成员变量链表struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法链表struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存列表struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表 #endif } OBJC2_UNAVAILABLE;
这个isa指针的指向就是该类对象的元类,每一个类都是它的元类的对象,元类是对类对象的描述,就像类是普通实例对象的描述一样。
每一个类里面声明的类方法,其本质就是把该类方法放到元类的方法列表上面,所以类在调用类方法时,可以想象成是元类的对象在调用一个实例方法。
A的父类是B,A的元类的父类是B的元类,B的父类是NSObject,NSObject的父类是nil,B元类的父类是NSObject的元类;特别注意的一点,NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,1、所以用NSObject 调用任意NSObject里面的实例方法都是可以成功的,
类和元类是一个闭环,实例指向类,类指向元类,元类指向跟元类,跟元类指向自身,根元类的父类是NSObject
元类是 Class 对象的类。每个类(Class)都有自己独一无二的元类(每个类都有自己第一无二的方法列表)。这意味着所有的类对象都不同。
NSObject里面的所有实力方法,任意类都可以通过类方法调用。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已
[obj foo] 等同于 obj_msgSend(obj,@selector(foo))
objc 在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类的方法列表以及其父类方法列表中寻找方法运行。如果在层层的寻找中均位找到方法的实现,
就会抛出unrecognized selector sent to XXX的异常,导致程序奔溃. 在这奔溃前,oc运行时提供了三次拯救程序的机会 1、Method resolution ,动态方法解析阶段 对应的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel, 当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法设计的目的是为了给类利用 class_addMethod 添加方法的机会。// void(*)()// 默认方法都有两个隐式参数,void eat(id self,SEL sel){ NSLog(@"%@ %@",self,NSStringFromSelector(sel));}pragma mark 消息转发第一步(实例) 如此便达到了,当此类调用未定义的实例方法时,自动调用eat函数,而避免了崩溃的情况。// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(eat)) { // 动态添加eat方法 // 第一个参数:给哪个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址) // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, @selector(eat), (IMP)eat, "v@:"); } return [super resolveInstanceMethod:sel];}
#pragma mark 消息转发第二步, 第一步失败后执行 #pragma mark 其实只要返回对象不为self 和 nil 就会把消息转发给返回的对象 - (id)forwardingTargetForSelector:(SEL)aSelector { NSString * str = NSStringFromSelector(aSelector); NSString * obj = [NSString stringWithFormat:@"testClass"]; NSLog(@"方法 %@ 即将转发给 Class %@",str,[obj class]); return obj; }
总结一下整个消息转发的流程:
代码:
#import "ViewController.h"@interface ViewController ()@property (weak, nonatomic) IBOutlet UILabel *displayLabel;- (IBAction)buttonTest:(UIButton *)sender;@end@implementation ViewController- (IBAction)buttonTest:(UIButton *)sender { NSLog(@"--1--"); [self performSelector:@selector(setText:) withObject:@"hello"];}+(BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"--2--"); return NO;}//+(BOOL)resolveClassMethod:(SEL)sel//{// NSLog(@"--2--");// return NO;//}-(id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"--3--"); return nil;}-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"--4--"); NSMethodSignature *signature= [super methodSignatureForSelector:aSelector]; if (!signature) { signature=[self.displayLabel methodSignatureForSelector:aSelector]; } return signature;}-(void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"--5--"); SEL seletor=[anInvocation selector]; if([self.displayLabel respondsToSelector:seletor]){ [anInvocation invokeWithTarget:self.displayLabel]; }}@end
问题:那我们只用最后一个接盘侠方法多好啊,为什么还需要前2个呢?
其实还与这3个方法的用途不同有关:运行期添加方法,用1;转发给另1个对象、改变方法时,用2;需要转发给多个对象时,用3;
参考:
链接:
链接: