Objective-C runtime 机制

Runtime使用C语言结构体表示对象,用C语言函数表示方法,这些C语言函数和结构体被Runtime封装后,咱们就能够在程序中执行建立,检查,修改类和对象和他们的方法面试

runtime
一、是由C、C++、汇编写成的api
二、OC运行时,装载到内存api

相对应的编译时,源代码翻译缓存

OC SWIFT JAVA 高级语言,不被机器所识别,须要编译成响应的机器语言,二进制并发

Objective-c程序有三种途径和运行时系统交互
一、经过Objective-c源代码,如@selector()
二、经过Foundation框架中NSObject的方法,如 iskindof
三、经过调用运行时系统给咱们提供的api接口,如objc_msgSend,objc_getClass框架

OC对象本质是结构体
调用方法就是发送消息 objc_msgSend
消息的组成:((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
第一个参数p消息的接收者,第二个参数sel_registerName("run")方法编号
imp 函数实现的指针,sel找到imp函数

查看关系图atom

OC的Class实际上是一个objc_class结构体的指针,下面是Class类的定义spa

typedef struct objc_class *Class;

查看objc/runtime.h中objc_class结构体的定义以下翻译

struct objc_class {
 Class isa OBJC_ISA_AVAILABILITY; //isa指针   
#if !__OBJC2__ Class
super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long 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的元类也都拥有,一、因此用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运行时提供了三次拯救程序的机会
 
 一、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];
}


 二、fowarding 方法转发,备援接收者阶段
 对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,
 此时,运行时询问可否把消息转给其余接收者处理,也就是此时系统给了个将这个 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; 
 }


 三、 fowarding 方法转发,完整消息转发阶段
 首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,假若返回值为nil,则runtime会发出doesNotRecognizeSelector:消息,引起异常,程序崩溃。若是返回了一个合理的函数签名,Runtime就会建立一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。参数 anInvocation 中包含未处理消息的各类信息(selector\target\参数...)。
 在这个方法中,能够把 anInvocation 转发给多个对象,与第二步不一样,第二步只能转给一个对象
 
 若是上述3个方法都没有来处理这个消息,就会进入 NSObject 的-(void)doesNotRecognizeSelector:(SEL)aSelector方法中,抛出异常

总结一下整个消息转发的流程:

 

代码:

#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;

 

参考:

连接:iOS消息转发机制详解

连接:OC最实用的runtime总结,面试、工做你看我就足够了!

相关文章
相关标签/搜索