欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OC Runtime 方法与消息传递

程序员文章站 2022-06-02 12:49:23
...

OC的方法调用我们已经很熟悉了,一段简单的代码,一个名为MyObject的类

#import <Foundation/Foundation.h>

@interface MyObject : NSObject

-(void)printSomeThing:(NSString *)age;
-(void)printSomeThing;

@end
#import "MyObject.h"

@implementation MyObject

-(void)printSomeThing:(NSString *)age {

    NSLog(@"name = %@, age = %@",NSStringFromClass([self class]), age);
}

-(void)printSomeThing{

    NSLog(@"come here");
}

@end

在一个ViewController中这样调用


#import "ViewController.h"
#import "MyObject.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    MyObject *object = [[MyObject alloc] init];
    [object printSomeThing:@"kobe"];
    [object printSomeThing];

}

通过 xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m 转成c++代码如下:

生成的ViewController.cpp,其中我们想要的核心代码

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));


    MyObject *object = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("alloc")), sel_registerName("init"));

    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing:"), (NSString *)&__NSConstantStringImpl__var_folders_db_k2cngcsd0g91h3dbfbbgrjdr0000gn_T_ViewController_c46f7e_mi_0);


    ((void (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing"));

}

objc_msgSend

从上面的代码中我们发现,方法调用是通过 objc_msgSend 来实现的,编译器会把我们的代码转换成objc_msgSend

id objc_msgSend(id self, SEL op, ...);

self: 要接受消息的对象

op: 处理消息的方法的selector

…: 可变参数

返回值: 方法的返回值

当objc_msgSend 遇到方法调用时,编译器会选择调用下面方法中的一个

objc_msgSend 普通对象的消息发送

objc_msgSend_stret 当返回值是数据结构体时的息发送

objc_msgSendSuper 父类的消息发送

objc_msgSendSuper_stret 当返回值是数据结构体时父类的消息发送

在Apple的开放源码中有这样一句话相当重要:

These functions must be cast to an appropriate function pointer type
before being called。
这个方法调用前必须强制转成合适的函数指针类型。

在C语言中如果把函数指针强制转化成另一种类型,会出现行为未定义的错误,所以在上面的方法中先把objc_msgSend转成void *
然后再转成所需要的类型(void ()(id, SEL, NSString )),因为调用前必须强制转成合适的函数指针类型。另一个就转成了(void (**)(id, SEL))。

SEL

定义了一个不透明的类型来表示一个方法选择器


typedef struct objc_selector *SEL;

方法选择器用来表示runtime中的方法名字,方法选择器就是被注册在OC Runtime中一个C字符串,当一个类被加载时,由编译器生成的selector会自动注册到runtime中。

使用sel_registerName可以添加一个新的selector到runtime中,或者从runtime中获取一个已经存在的runtime。上面的代码中多次使用。

在使用selector时必须使用sel_registerName的返回值或者@selector()或者NSSelectorFromString(),不能直接把C字符串转成一个SEL。

在OC中如果方法相同,那么他们就会具有相同的selector,在runtime中他们就是同一个C字符串。在同一个类中不可能存在相同的方法,所以不用考虑如何区分。对于不同类中的相同的selector,用上一篇提到的isa来区分就ok了。

IMP

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

selector只是一个方法对应的名称字符串,而真正的实现是由IMP来实现的。IMP 就是为了表示函数的地址,本质就是一个函数指针,它指向了真正的函数实现过程。

那么SEL跟IMP是如何关联到一起的?

METHOD

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

Method把SEL跟IMP联系到了一起,这样通过SEL就可以找到相应的method然后对应到其相应的实现IMP.


id method_invoke(id receiver, Method m, ...); //调用method的实现
SEL method_getName(Method m);  获取method的名字 ,返回一个SEL
unsigned int method_getNumberOfArguments(Method m);  返回方法参数个数

更多操作函数

IMP中的两个隐藏参数:

  1. self : 接收消息的对象
  2. _cmd : 此IMP对应的SEL

获取方法地址: methodForSelector

void (*setter)(id, SEL, BOOL);  // 函数指针
int i;

setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)]; //给setter函数指针赋值
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES); //函数调用

objc_msgSend动态绑定过程

  1. 根据SEL找到相应的IMP,由于不同的类可能具有相同的selector,所以在IMP的过程中要通过receiver的class来找到精确的IMP
  2. 调用IMP,把receiver,及参数传入IMP
  3. 返回其返回值

OC Runtime 方法与消息传递

上图显示了消息的分发流程,当向一个对象发送消息时,首先根据对象的isa找到其对应的class,然后到class的cache中查找所要执行的selector,如果不存在继续到其方法列表中查找,如果没有,继续向上到其父类中查找,直到根类,如果在根类都没有找到相应的方法,那么就会报错,如果找到了就执行selector对应的IMP。

动态方法绑定

当我们向一个对象发送消息时,如果消息对应的selector不存在时就会出错。对于这样的问题抛出错误是一个相当明智的解决方法,这样就会杜绝很多bug。对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中我们可以动态的给selector绑定一个IMP, 这样selector就有了一个实现。


void dynamicMethodIMP(id self, SEL _cmd) {

    NSLog(@"This is a  dynimaic ");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(dynimaicSel)) {

        class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "aaa@qq.com:");
        return YES;
    }

    return [super resolveInstanceMethod:sel];
}

dynimaicSel 没有实现, 通过class——addMethod为其添加了一个实现,相当于动态添加了一个Method。

当向一个对象发送一个未知的消息时,在系统报错前我们有三个时机来解决这问题,第一时机就是我们上一篇提到的使用resolveInstanceMethod:来动态加载一个实现,第二时机就是使用- (id)forwardingTargetForSelector:(SEL)aSelector把消息转发给另一个对象,第三个时机就是Message Forwarding。

-(id)forwardingTargetForSelector:(SEL)aSelector

用于把未知消息转发给另一个对象
当在类中实现此方法或者从父类继承来,方法的返回值non-nil就会作为消息的新的接收者。如果是nil,没有新的接收者时只需要return [super forwardingTargetForSelector:aSelector];

Forwarding

当对象收到一个未知的消息时,如果上面的步骤没有对消息进行都处理,在报错之前,runtime会向对象发送forwardInvocation:消息并且会携带一个NSInvocation对象作为唯一的参数。NSInvocation携带了原始的消息和参数。

在调用forwardInvocation方法之前,必须为调用的方法提供一个签名,我们需要重写methodSignatureForSelector方法,返回方法的签名。 如查返回nil,则不会继续往下执行,不会调用forwardInvocation方法。得到的错误仍然会是unrecognized selector

定义了两个类,一个Person, 一个Plane, 在Plane中定义了一个 -(void)fly;
在Person中重写了下面的函数来处理向person发送fly消息。

// 返回要调用方法的签名:instanceMethodSignatureForSelector
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        if ([Plane instanceMethodSignatureForSelector:aSelector]) {

            sig = [Plane instanceMethodSignatureForSelector:aSelector];
        }
    }

    return sig;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation{

    if ([Plane instanceMethodSignatureForSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:[Plane new]]; // 调用方法
    } else {
        [super forwardInvocation:anInvocation];
    }
}