美文网首页
OC,objc_msgSend()函数做了哪些事

OC,objc_msgSend()函数做了哪些事

作者: BangRaJun | 来源:发表于2018-09-11 15:48 被阅读0次

    OC,objc_msgSend()

    简介:用一句话简介消息传递那就是:用一个 C语言函数,向一个实例传递一个字符串,实例拿到字符串后与自己的method_list中的SEL比较,遇到一样的就找到对应IMP执行。SEL就是一个字符串,IMP就是一个函数指针,在一个Method结构体中封装着这两者,因此他俩一一对应。

    • 在向一个实例发送消息时[obj doSomthing];实际上是调用的这个函数objc_msgSend(obj,@selector(doSomething));(该函数在<objc/message.g>中)
    • 接下来将从头到尾表述在这个函数中究竟干了多少事情。
    • 阶段1(对于能独立解决的问题):
    1. 寻找类:根据上一篇的OC是如何使C语言变得面向对象的,知道一个objc_object结构体中就只有isa(一个指向objc_class的指针),因此需要从他的objc_class结构体中查找函数,于是顺着isa指针找到了这个objc_class。
    2. 访问cache:objc_cache结构体中装着一个method结构体的数组,先在这个数组中遍历method,对于每一个method,要对照method中的SEL与上面传递进来的@selector(doSomething)是不是一样,如果一样就确认是需要调用它的IMP。
    3. 访问method_list:如果在步骤2中没有找到一样的SEL,就需要遍历所有的函数了,与遍历cache时一样,对照SEL,选取IMP。如果在这个步骤中仍然不能找到对应的SEL,那么就会进入阶段2(不能独立解决的问题)。
    4. 假如通过上述1-3过程找到了method,之后执行对应的IMP就可以了。
    • 阶段2(对于不能独立解决的问题):
    1. 寻找父类:在阶段1中,当在cache和method_list中不能找到一致的SEL时,msgSend函数会继续往祖坟上刨,它根据objc_class中的super指针(指向父类的objc_class结构体的指针)找到该类的父类,然后在父类的cache和method_list中执行阶段1的2、3步一样的操作。
    2. 寻找父类的父类:如果在这个时候还是不能找到对应的SEL,就会继续根据super指针继续寻找父类,直到super指针是nil,说明没有一个人可以响应这条消息。这个时候如果没有一些阶段3的防护措施就会报错了,通常是unrecognized selector的错误。
    • 阶段3(所有子类和父类都不能解决的问题):
    • 对于我们没有预测到的一些错误调用(使用performSelector传入一个随便的selector)或者在实现上的一些失误(比如你答应了某个协议去实现他的一些方法,然后在implement中没有写对应的实现,而协议的另一端正在调用协议中的方法)。有三种补救方式允许我们避免程序的崩溃:在resolve(Instance/Class)Method中做检查、提供另外一个可以供转发的对象、使用NSInvocation重新调用。
    1. 在resolveMethod中做检查(这里均以实例方法为例):首先进入resolveInstanceMethod函数,注意这个方法不会影响阶段1和阶段2的所有过程,也就是说,执行了这个方法之后,如果仍然找不到对应的SEL,依然是会报错的。因此我们可以在这个方法中,动态地添加其实现。创建TestObj类如下,啥属性啥方法都没有,却能向他发送任何实例消息。原因就是,在resolveInstanceMethod方法中接收到一个找不到的SEL,不论这个SEL叫什么,我们都给它一个默认的实现defaultDealMethod。因此,TestObj永远都不会引起崩溃。
    
    #import <Foundation/Foundation.h>
    
    @interface TestObj : NSObject
    
    @end
    
    
    #import "TestObj.h"
    #import <objc/runtime.h>
    
    @implementation TestObj
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSString *str = NSStringFromSelector(sel);
        NSLog(@"TestObj没有找到方法:%@,来到了resolveMethod中",str);
        class_addMethod(self, sel, (IMP)defaultDealMethod, "v@:");
        return YES;
    }
    
    void defaultDealMethod(id self, SEL sel){
        NSString *str = NSStringFromSelector(sel);
        NSLog(@"DefaultDealMethod:%@",str);
    }
    @end
    
    1. 第二个保障,就是为这个不能处理的对象找一个可以求助的实例。为了告知这个实例是谁,需要重写方法forwardingTargetForSelector:(SEL)sel方法。既然刚刚有一个无所不能的TestObj方法,那就让他来担当这个求助对象吧,虽然他没做什么实质工作,但能保证不崩溃。我们在测试的ViewController中这样写,虽然随便调用了两个没有实现的方法,但是并没有崩溃,因为他成功向TestObj求助了,而TestObj为了帮助它,在自己的方法列表中加入了两个新的方法,这样就可以查找到SEL并成功执行。
    - (void)viewDidLoad {
        [super viewDidLoad];
        objc_msgSend(self, @selector(asdasdasdadsas));
        objc_msgSend(self, @selector(asdakjsdajdjka));
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        return [[TestObj alloc] init];
    }
    

    3.第三个保障:使用Invocation调用(个人不大明白这样做的必要性),Invocation类似设计模式中的命令模式,就是将一个命令或者是调用封装为一个Obj,Invocation中大体包含如下属性:一个MethodSignature(格式)、target(目标,也就是消息发送的对象)、selector(方法名);一个MethodSignature中又大体包含如下属性:参数个数、方法长度、返回值类型、返回值长度。由此可见,一个Invocation就是对一次调用函数各方面格式、目标的封装。因此我们需要做的就是在最后一条保障中,规定一个Invocation,并使用这个Invocation完成调用。

    - (id)forwardingTargetForSelector:(SEL)aSelector{
        //return [[TestObj alloc] init];
        return nil;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        [anInvocation invokeWithTarget:[[TestObj alloc] init]];
    }
    
    • Signature是Invocation的组成元素,因此在得到Invocation前需获取到这些格式信息,对于这个格式:第一位是返回值,可以是c(char)、i(int)、s(short)、l(long)等等。第二位是接收的第一个参数self,因此是"@",第三位是selector用":"表示,之后的位置就都是自定义的参数了,这些类型在官网上可以查到,后面给出一些常用的。举个例子,这样一个函数:
    - (instancetype)initWithName:(NSString *)name height:(float)height weight:(float)weight father:(id)father mother:(id)mother;
    
    
    • 它的格式就可以这样表示"@@:@ff@@";
    • 每一位分别对应 instance返回值、默认参数self、默认参数SEL、参数name、参数height、参数weight、参数father、参数mother。
    char:c
    int:i
    short:s
    long:l
    longlong:q
    //上面这些都大写就代表是 unsigned的,如 unsigned int:I
    float:f
    double:d
    bool:B
    void:v
    id:@
    SEL: :
    
    • 如果我们在methodSignature中返回了一个非nil的signature,系统就会为我们创建一个invocation并调用forwardingInvocation方法,在该方法中我们会得到之前预定好的格式的invocation,我们就可以invoke这个invocation了。

    • 当然,如果最后第三个保障也没有好好利用的话就只有崩溃了。

    另外,如果xcode不允许使用带参数的msgSend()函数,在项目的buildSettings中搜索strict,有一个Enable strict checking of objc_msgSend call选项,把他置为NO,之后就好用了。

    以上就是OC消息传递的全部过程

    结束

    相关文章

      网友评论

          本文标题:OC,objc_msgSend()函数做了哪些事

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