ios底层原理

消息发送机制和运行时

2017-08-28  本文已影响0人  富有的心

消息发送机制定义

OC的函数调用称为消息发送。
OC的消息发送属于动态调用过程。即在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错。

OC是如何实现动态调用

假如在OC中写了这样的一个代码:
[obj makeText];
其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成
objc_msgSend(obj,@selector(makeText));
首先我们来看看obj这个对象,iOS中的obj都继承于NSObject。
@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; }
在NSObjcet中存在一个Class的isa指针。然后我们看看Class:

struct objc_class {
  Class isa; // 指向metaclass
  Class super_class ; // 指向其父类
  const char *name ; // 类名
  long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
  long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
  long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
  struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
  struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
  struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
  struct objc_protocol_list *protocols; // 存储该类遵守的协议
    }

最后,消息发送之后动态查找对应的方法

1 .编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector (makeText));
2 .通过obj的isa指针找到obj对应的class。
3 .在Class中先去cache中通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。
4 .若能找到,通过method中的函数指针跳转到对应的函数中去执行。最后将method加入到cache中,以方便下次查找。
5 .当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给 方法实现,同时,它还将传递两个隐藏的参数:1、接收消息的对象 2、方法选标
objc_msgSend(receiver, selector, arg1, arg2, ...)

那么,消息发送机制能够给我们的编程带来些什么新鲜血液呢?

 setter(targetList[i], @selector(setFilled:), YES);
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];

方法指针的第一个参数是接收消息的对象(self),第二个参数是方法选标(_cmd)。这两个参数在方法中是隐藏参数,但使用函数的形式来调用方法时必须显示的给出。

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
        if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
        return YES;
}
        return [super resolveInstanceMethod:aSEL];
}
@end

在进入消息转发机制之前,respondsToSelector:和instancesRespondToSelector: 会被首先调用。您可以在这两个方法中为传进来的选标提供一个IMP。如果您实现了resolveInstanceMethod:方法但是仍然希望正常的消息转发机制进行,您只需要返回NO就可以了。
例如,摘自网上的一段示例代码:

@interface SomeClass : NSObject
@property (assign, nonatomic) float objectTag;
@end

@implementation SomeClass
@dynamic objectTag;  //声明为dynamic
//添加setter实现
void dynamicSetMethod(id self,SEL _cmd,float w){
    printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
                                 cStringUsingEncoding:NSUTF8StringEncoding]);
    printf("%f\n",w);
    objc_setAssociatedObject(self, ObjectTagKey, [NSNumber numberWithFloat:w], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//添加getter实现
void dynamicGetMethod(id self,SEL _cmd){
    printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
                                 cStringUsingEncoding:NSUTF8StringEncoding]);
   [objc_getAssociatedObject(self, ObjectTagKey) floatValue];
}

//解析selector方法
+(BOOL) resolveInstanceMethod: (SEL) sel{

    NSString *methodName=NSStringFromSelector(sel);
    BOOL result=NO;
    //动态的添加setter和getter方法
    if ([methodName isEqualToString:@"setObjectTag:"]) {
        class_addMethod([self class], sel, (IMP) dynamicSetMethod,
                        "v@:f");
        result=YES;  
    }else if([methodName isEqualToString:@"objectTag"]){
        class_addMethod([self class], sel, (IMP) dynamicGetMethod,
                        "v@:f");
        result=YES;
    }
    return result;
}
- negotiate
{
  if ( [someOtherObject respondsTo:@selector(negotiate)] )
  return [someOtherObject negotiate];
  return self;
}

以上方法的缺陷是:如果有很多消息都希望传递给其它对象时,您必须为每一种消息提供方法实现。此外,这种方式不能处理未知的消息。
解决办法就是:使用forwardInvocation:方法。
因为当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 forwardInvocation:消息通知该对象。通过实现您自己的 forwardInvocation: 方法,您可以在该方法实现中将消息转发给其它对象。要转发消息给其它对象,forwardInvocation:方法所必须做的有:1、决定将消息转发给谁 2、将消息和原来的参数一块转发出去。转发消息后的返回值将返回给原来的消息发送者。
如下所示:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
[anInvocation selector]])
    //消息可以通过 invokeWithTarget:方法来转发
    [anInvocation invokeWithTarget:someOtherObject];
else
    [super forwardInvocation:anInvocation];
 }

所以,forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。 或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不 同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。

注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以, 如果您希望您的对象将 negotiate 消息转发给其它对象,您的对象不能有 negotiate 方法。否则, forwardInvocation:将不可能会被调用。

if ( [aWarrior respondsToSelector:@selector(negotiate)] )

返回值是NO,尽管该对象能够接收和响应negotiate。大部分情况下,NO 是正确的响应。但不是所有时候都是的。例如,如果您使用消息转发来创建一个代理对象以扩展某个类的能力,这儿的消息转发必须和继承一样,尽可能的对用户透明。如果您希望您的代理对象看起来就象是继承自它代表的对象一样,您需要重新实现 respondsToSelector:和 isKindOfClass:方法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
   if ( [super respondsToSelector:aSelector] )
         return YES;
   else {
       /* Here, test whether the aSelector message can *
       * be forwarded to another object and whether                 that *
       * object can respond to it. Return YES if it can. */
   }
   return NO;
}
除了 respondsToSelector:和 isKindOfClass:外,instancesRespondToSelector: 方法也必须重新实现。如果您使用的是协议类,需要重新实现的还有 conformsToProtocol:方法。 类似地,如果对象需要转发远程消息,则 methodSignatureForSelector:方法必须能够返回实际 响应消息的方法的描述。例如,如果对象需要将消息转发给它所代表的对象,您可能需要如下的 methodSignatureForSelector:实现:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
     NSMethodSignature* signature = [super methodSignatureForSelector:selector];
     if (!signature) {
     signature = [surrogate methodSignatureForSelector:selector];
     }
     return signature;
}

您也可以将消息转发的部分放在一段私有的代码里,然后从 forwardInvocation:调用它。
其实,这一段我还没有摸索透。。。

上一篇 下一篇

猜你喜欢

热点阅读