美文网首页
iOS消息传递

iOS消息传递

作者: 你好8828 | 来源:发表于2017-10-31 17:59 被阅读0次

在iOS开发中经常会遇到unrecognized selector sent to instance 0x100111df0'的问题,这是为什么呢,从字面上理解来说是无法识别的selector子发送给对象,其实调用一个不存在的方法就会遇到这个问题。
严格来说iOS中不存在方法调用的说法,应该说是消息的传递。

消息传递和函数调用的区别就是,你可以在任意的时候对一个对象发送任何消息,而不需要在编译的时候声明。但是函数调用就不行。

标题1

- (void)foo {
}
[self foo];

以上的是一个简单的例子,相当于向self对象传递foo方法,objective-C会在runtime时期将这个转换为

objc_msgSend(self, foo)
objc_msgSend(id theReceiver, SEL selectot,……)

这里的objc_msgSend是一个可变参数的函数,接受大于等于两个参数。第一个参数是id类型的,可以是任何对象或者类。selector是一个SEL类型的参数。那么SEL是什么呢?SEL是对方法的一种封装。其实就是个方法名或者说是签名,方法真正的实现在IMP中。
方法的链表大概是这个样子。

typeof struct objc_method {

    SEL method_name
    IMP method_imp
    ……………………
}

SEL相当于门牌号,IMP相当于真正的住处,门牌号可以随便搞,但是瞎指就会出问题。
我们下来看一下在OC中传递一个消息会发生什么事情。
调用一个`objc_msgSend(id theReceiver, SEL selectot,……)方法系统执行的步骤为:
0.判断receiver是否为nil,如果是nil的话则不往下执行,返回nil,这就是为什么在oc中一个nil发送消息不会引起奔溃。
1、从方法的缓存中查找 被调用过的方法会存在缓存里面,每个类都会有一个表来存被调用过的方法,以便下次更快的调用。
2、从本类的方法表(dispatch table)中查找方法寻找selector,找到则写入缓存,返回方法。否则再从父类中查找方法,如此往复,直到达到基类。如果找不到则执行方法的动态解析。
3、方法的动态解析: 调用 + (BOOL)resolveInstanceMethod:(SEL)sel方法来查看是否能够返回一个selector,如果存在则返回selector。不存在进入下一步。
4、备用接受者 - (id)forwardingTargetForSelector:(SEL)aSelector这个方法来询问是否有接受者可以接受这个方法呀。如果有人接受,则交给它处理,就好像一切都没发生过一样。
5、方法的转发: 如果到这一步还不能够找到相应的Selector的话,就要进行完整的方法转发过程。调用方法(void)forwardInvocation:(NSInvocation *)anInvocation
最后还是没有找到的话就只有呵呵了,这时候unrecognized selector sent to instance 0x100111df0'的错误就来了。

image.png

以上是处理消息的流程图。这里可以看到查找一个方法需要经过很多的步骤,所以我们很多次机会来弥补这种错误,但是越往后面处理消息所消耗的代价越大。我们从第一步开始看,最好能够在一开始就找到相应的selector,那么他就会把方法缓存起来,等再次调用相同的方法的时候就会直接从缓存中取出来,那效率很高,和直接用c调用的速度慢不了多少。在没有缓存的情况下会从类的方法表里面进行查找。一个对象会有一个isa指针来指向自己所属的类。而类则会有一个方法表(dispatch table),用于将selector和真正实现的内存地址对应起来。另外还有一个指针会指向父类,这样就可以逐级向上查找直到基类。如下图

22.png

方法的动态解析

- (instancetype)init {

    if (self = [super init]) {
        [self performSelector:@selector(creash)];


    }
    return self;
}

这里我调用了creash,但是方法并没有被实现,所以会出错。
我们来实现下面的方法,不要忘记导入头文件#import <objc/runtime.h>

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"creash"]) {
        class_addMethod(self,
                        sel,
                        (IMP)askMeWhenCreash,
                        "");

        return YES;
    }
    return NO;
}

void askMeWhenCreash() {
    NSLog(@"creash不要慌,来执行这个");
}

在creash方法没找到之后,程序首先进入resolveInstanceMethod方法,我们先来判断方法名是否为creash,如果是的话我们在这里用class_addMethod(Class cls, SEL name, IMP imp, const char *types)方法动态的给他添加方法的实现。第三个参数imp就是,我们将它设为自己定义的一个方法void askMeWhenCreash(),最后return YES表示我们已经处理,不会再报错。

备用接受者

走到这一步我们其实能做的已经很少了,- (id)forwardingTargetForSelector:(SEL)aSelector方法只是给当前的selector再找一个新的接受者,并不能做其他的改变。

NSString *result = [self performSelector:@selector(lowercaseString)];

我们来调用一下lowercaseString方法,这个方法显然是NSString才有的方法。所以我们可以把它指派给一个NSString类型的对象。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return @"APPLE";
}

这里将lowercaseString方法找了个新的接受者,外界好像看起来什么都没有发生,但其实内部已经把接受者从self变成了APPlE对象。

消息的转发

还是上面那个例子,我们继续调用

[self performSelector:@selector(testForward:) withObject:@"arg1sdfsdfsdf"];

要使用消息的转发必须要覆盖两个方法在methodSignatureForSelector和forwardInvocation
前者永远为方法创建一个有效的签名。必须实现。

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

    return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}

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

    [anInvocation setSelector:@selector(forwardTo:)];
    NSString *arg1;
    [anInvocation getArgument:&arg1 atIndex:2];
    [anInvocation invokeWithTarget:self];
}
- (void)forwardTo:(NSString *)arg1 {

    NSLog(@"%@",arg1);
}

输出
2015-08-21 15:23:37.560 objc_msgSendTest[18793:1974024] arg1sdfsdfsdf
这里我们把未实现的testForward方法转发到了(void)forwardTo:(NSString *)arg1方法上去
上面有一个小问题就是关于参数的问题,明明只有一个参数为什么Index为2呢,这是因为在objective-C中的方法默认隐藏了两个参数,self和_cmd。这样说的话就很容易来解释方法签名中的"v@:@"是什么鬼,v表示返回值void,接下来就是三个参数。

方法的缓存

深入理解Objective-C:方法缓存

相关文章

  • iOS模式之二:代理模式

    iOS中消息传递方式 在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式。 通知:在iOS中由通知...

  • iOS开发需要掌握的技能篇

    基础知识储备1. iOS的消息传递方式-1.通知2.iOS的消息传递方式-2.代理3.iOS的消息传递方式-3.B...

  • iOS 消息传递

    在Objective-C 中,对象调用方法,这是很常见的事情。也许是灯下黑,就因为它很常见,所以我以前都没有深入地...

  • iOS消息传递

    在iOS开发中经常会遇到unrecognized selector sent to instance 0x1001...

  • iOS消息传递

    在iOS开发中经常会遇到unrecognized selector sent to instance 0x1001...

  • IOS消息传递机制

    ios的消息传递机制分为三个阶段:消息发送阶段,动态解析阶段,消息转发阶段。 消息发送阶段: 当ios的对象调用方...

  • 几种消息传递机制的比较

    本文主要借鉴了 objc上的文章 消息传递机制 iOS中,消息传递机制主要有 5种 KVO Notificatio...

  • IOS 消息传递与消息转发

    1、方法method和selector(选择子)有什么关系 在 Objective-C 中,selector,Me...

  • Mach消息发送机制

    目录 Mach基础 Mach作用 Mach消息简单消息复杂消息端口 消息传递实现 Mach基础 Mach是iOS的...

  • 消息传递的方式

    摘自消息传递的方式[https://github.com/liberalisman/iOS-InterviewQu...

网友评论

      本文标题:iOS消息传递

      本文链接:https://www.haomeiwen.com/subject/hpabpxtx.html