OC Runtime 方法与消息传递

来源:互联网 发布:nba2017球员数据 编辑:程序博客网 时间:2024/06/09 13:33

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_PROTOTYPEStypedef void (*IMP)(void /* id, SEL, ... */ ); #elsetypedef 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的名字 ,返回一个SELunsigned 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. 返回其返回值

消息传递过程

上图显示了消息的分发流程,当向一个对象发送消息时,首先根据对象的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, "v@:");        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];    }}
0 0
原创粉丝点击