Runtime-消息机制

Objective-C是一门动态语言,类型的判断、类的成员变量、方法的内存地址都是在程序的运行阶段才最终确定,并且还能动态的添加成员变量和方法。这意味着即使调用对象一个没有实现的方法,编译也能通过,甚至一个对象它是什么类型并不是表面我们所看到的那样,只有运行之后才能决定其真正的类型。因此OC具有多态动态类型动态绑定特性。

正是因为其动态特性,在OC中调用方法并不是一个简单的函数调用过程,它并不直接调用方法,而是运用Runtime机制实现函数调用,称之为消息发送

Runtime

Runime运行时,是OC中一套底层的C语言API,底层都是基于它来实现的。在运行时,我们所编写的代码会转换为C语言运行。具体内容可以参考:runtime概述

在OC中发送一条消息:[receiver message]
会转为调用底层函数:id objc_msgSend(id self, SEL op, ...)
让我们先看看这个函数的参数和返回值
id在OC中表示任意类型的对象,其结构如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

显然id是一个objc_objec结构体类型指针,其内部有一个Class类型的isa指针,指向对应的类。
SEL是对方法的包装,返回对应的编号

struct objc_class {
    Class _Nonnull isa             指向自身对应的类;
#if !__OBJC2__
    Class _Nullable super_class            指向父类                  
    const char * _Nonnull name                               
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list * _Nullable ivars                  
    struct objc_method_list * _Nullable * _Nullable methodLists    类中的方法信息列表                 
    struct objc_cache * _Nonnull cache         调用过的方法信息列表             
    struct objc_protocol_list * _Nullable protocols          
#endif
}

上面是class的结构

消息发送过程一 --- 函数在类中实现

当发送一条消息时,object_getClass(id obj)通过id的isa指针找到对象的对应的类class,在class中先去cache中 通过SEL查找对应函数method(cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到,方法列表methodList中查找,若能找到,则将method加 入到cache中,以方便下次查找。否则到对应的super_class中查找,如此循环直到NSObject。

我们具体看OC如何用runtime实现函数调用:

自定义Person对象
@interface Person : NSObject
+ (void)eat;
- (void)eat;
- (void)run:(int)age;
@end
@implementation Person
+ (void)eat
{
    NSLog(@"类方法-吃东西");
}
- (void)eat
{
     NSLog(@"实例方法-吃东西");
}
- (void)run:(int)age
{
    NSLog(@"跑了%d米",age);
}
@end
-------------------------UIViewController-----------------------------
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [[Person alloc] init];
    objc_msgSend(person, @selector(eat));
    objc_msgSend(person, @selector(run:),10);

    Class personClass = [Person class];
    objc_msgSend(personClass, @selector(eat));
}
-------------------------console-----------------------------
[1373:101045] 实例方法-吃东西
[1373:101045] 跑了10米
[1373:101045] 类方法-吃东西

以上就是发送消息调用类中实现的方法的过程。


消息发送过程二 --- 函数在类中未实现,动态解析方法

当向receiver发送message时,若SEL对应的函数未在类或者类的父类中实现,程序并不会立即crash,而是对message进一步转发,调用_objc_msgForward(id receiver, SEL sel, ...),具体表现在类中对应的方法为:

1.+ resolveInstanceMethod:(SEL)sel // 对应实例方法
  + resolveClassMethod:(SEL)sel // 对应类方法
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (void)forwardInvocation:(NSInvocation *)anInvocation

注意:以上方法优先级依次递减,高优先级方法消息转发成功不会再执行低优先级方法

现在为Person实例对象发送一条@selector(wy_sleep:)消息:

[person performSelector:@selector(wy_sleep:) withObject:@100];

因为person未实现@selector(wy_sleep:),消息转发转而调用+ (BOOL)resolveInstanceMethod:(SEL)sel:,判断类是在运行时动态的添加方法实现

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(wy_sleep:)) {
        
        /*
         cls:给哪个类添加方法
         SEL:添加方法的方法编号是什么
         IMP:方法实现,函数入口,函数名
         types:方法类型
         最后一个参数请查看文档Help - API reference - runtime
         */
        class_addMethod([self class], sel, (IMP)wy_sleep, "v@:@");
        
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void wy_sleep(id self, SEL _cmd, id param1){
    
    NSLog(@"调用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}

-------------------------console-----------------------------
[1553:136714] 调用wy_sleep  wy_sleep: 100

其中class_addMethod(Class cls, SEL name, IMP imp, const char *types)为类添加新的方法实现。


消息发送过程三 --- 函数在类中未实现且无动态解析,快速转发消息

当调用的方法没有在类中实现,且没有动态添加时,则消息继续转发,判断类中是否预备了备用的消息接受者

- (id)forwardingTargetForSelector:(SEL)aSelector
简单、快速,但只能只能指定一个转发对象,无法对消息进行操作

 定义对象A、B
-------------------------A-----------------------------
@interface A : NSObject
- (void)prinAName;
@end
@implementation A
- (void)prinAName
{
    NSLog(@"Hello World! My name is A");
}
@end

-------------------------B-----------------------------
@interface B : NSObject
- (void)prinBName;
@end
@implementation B
- (void)prinBName
{
    NSLog(@"Hello World! My name is B");
}
@end

实现Person中的的快速消息转发:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (class_respondsToSelector([A class], aSelector)) {
        return [[A alloc] init];
    }
    
    B *b = [[B alloc] init];
    if ([b respondsToSelector:aSelector]) {
        return b;
    }
    return [super forwardingTargetForSelector:aSelector];
}

向person实例发送相应的消息:

[person performSelector:@selector(prinAName)];
[person performSelector:@selector(prinBName)];

输出结果:
[1962:235819] Hello World! My name is A
[1962:235819] Hello World! My name is B


消息发送过程四 --- 函数在类中未实现且无动态解析,完整的消息转发

除了设置备用消息接受者,快速转发消息外,还能将完整的消息转发给实现的对象

-(void)forwardInvocation:(NSInvocation *)anInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [A instanceMethodSignatureForSelector:aSelector];
    if (methodSignature == nil) {
        methodSignature = [B instanceMethodSignatureForSelector:aSelector];
    }
    if (methodSignature == nil) {
        methodSignature = [super methodSignatureForSelector:aSelector];
    }
    return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = [anInvocation selector];
    A *a = [[A alloc] init];
    B *b = [[B alloc] init];
    if ([a respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:a];
    } else if ([b respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:b];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

消息发送过程五 --- Crash

当前面消息发送的4个过程都没有实现时,程序调用- (void)doesNotRecognizeSelector:(SEL)aSelector崩溃

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"无法实现方法,crash");
}

总结:
OC中函数调用通过发送消息实现,分为五个步骤,如下:

Runtime-消息机制_第1张图片
消息发送流程

你可能感兴趣的:(Runtime-消息机制)