Runtime第五篇-方法和消息发送

2017-09-20  本文已影响9人  lzh_coder

先把Method的内存模型摆出来:

typedef struct objc_method *Method;

struct objc_method {

SEL method_name;

char* method_types;

IMP method_imp;

}

1,SEL-选择器

typedef struct objc_selector * SEL;

runtime并没有给出SEL的具体结构。实际上SEL是对方法名的一种映射。它出现的需求在于,方法的执行首先要找到方法Method结构体,在method的链表中,怎样去定位一个结构体。我们看Method的内存结构,一共三个字段。不用分析,只能通过method_name来定位到这个结构体,它是一个SEL,它是由方法名转化得来的。所以,SEL是用来定位Method的,进而执行Method的method_imp。

Objective-C的这种设计决定了Objective-C这门语言不支持重载。

SEL的相关操作:

const char * sel_getName ( SEL sel );

2,IMP-函数指针

id (*IMP)(id, SEL, ...)

3,Method的操作

3.1 执行一个方法

id method_invoke ( id receiver, Method m, ... );

3.2 获取方法名

SEL method_getName ( Method m );

3.3 获取Method的IMP

IMP method_getImplementation ( Method m );

3.4 获取Method的类型编码

const char * method_getTypeEncoding ( Method m );

3.5 获取方法返回值的类型字符串

char * method_copyReturnType ( Method m );

3.6 设置Method的imp

IMP method_setImplementation ( Method m, IMP imp );

3.7 交换Method的imp

void method_exchangeImplementations ( Method m1, Method m2 );

4,消息发送

Objective-C中的方法调用都是在runtime动态调用的,实际上是执行了objc_msgSend方法。

objc_msgSend(receiver, selector)

如果有参数:

objc_msgSend(receiver, selector, arg1, arg2, ...)

这个方法会动态查询方法的imp,class_getMethodImplementation,如果正常流程没有查询到,就会返回msg_forward的imp,就会走消息转发流程。

具体细节是这样的:

当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,首先会在类的结构体的cache里面查找selector,如果没有查找到,就去方法链表里面查找方法的selector。如果当前类没有找到selector,则通过类结构体中的superClass指针找到其父类,在父类中依旧是先查找cache,然后查找方法链表,如果找不到,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。另外,在方法链表里面查询到的方法会被放到cache里面,提高selector的查找速度。如果最终还是找不到,就走消息转发流程。

5,消息转发

一般我们不确定一个实例对象是否响应某个方法的时候,我们会使用

- (BOOL)respondsToSelector:(SEL)aSelector;来确认一下,避免报unrecognized selector exception。

这里要讲的是,如果不调用respondsToSelector加保护,出现selector定位不到的时候怎么办,它的机制是怎么样的。

这个机制一共分三步,这一套机制NSObject里面提供了接口:

1,动态方法解析。--给没有实现的selector提供一种实现。

+ (BOOL)resolveInstanceMethod:(SEL)sel;//给实例selector提供一种实现

+ (BOOL)resolveClassMethod:(SEL)sel;//给类selector提供一种实现。

eg.

void functionForMethod1(id self, SEL _cmd) {

NSLog(@"%@, %p", self, _cmd);

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

     NSString *selectorString = NSStringFromSelector(sel);

     if ([selectorString isEqualToString:@"method1"]) {

            class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");

     }

    return [super resolveInstanceMethod:sel];

}

2,备用接受者。--更换消息接受者。

- (id)forwardingTargetForSelector:(SEL)aSelector; //提供一个新的消息接受者。

基于runtime的这个机制,我们可以做一些松耦合的东西。比如做一个中心类,client调用中心类的方法,中心类通过这一机制进行方法分发。

3,消息重新转发。--这一步不仅可以更改消息接受者,还可以更改消息的参数。

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

此外需要重写:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

eg.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

 {

        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

        if (!signature) {

                  if ([ARespondClass instancesRespondToSelector:aSelector]) {

                  signature = [ARespondClass instanceMethodSignatureForSelector:aSelector];

                  }

      }

      return signature;

}

- (void)forwardInvocation:(NSInvocation *)anInvocation 

{

       if ([ARespondClass instancesRespondToSelector:anInvocation.selector]) {

            [anInvocation invokeWithTarget:ARespondClassObject];

       }

}

在这个例子里面并没有修改NSInvocation,没有修改它的参数。在这一步里面,实际上可以做更加灵活的消息转发,必须追加一个消息参数等等。

组合和继承是面向对象的一个重要话题。runtime的消息转发机制提供了一种优雅的组合实现机制。对外,client调用中心类的方法,通过performSelector,对内,中心类对消息进行分发。

上一篇 下一篇

猜你喜欢

热点阅读