OC的方法调用的本质是runtime底层调用objc_msgSend,下面我们来分析一下objc_msgSend的具体实现

1、创建一个Person类,有一个对象方法eat和一个实例方法run,并分别调用

@interface Person : NSObject
+(void)eat;//类方法
-(void)run;//实例方法
@end


[Person eat];
    
Person *p = [Person new];
[p run];

每个对象都会有一个它所属的类。这是面向对象的基本概念,但是在OC中,这对所有数据结构有效。任何数据结构,只要在恰当的位置具有一个指针指向一个class,那么,它都可以被认为是一个对象。
在OC中,一个对象所属于哪个类,是由它的isa指针指向的。这个isa指针指向这个对象所属的class。

实际上,OC中对象的定义是如下的样子:

typedef struct objc_object {
      Class isa;
}*id;

这个定义表明:任何以一个指向Class的指针作为首个成员的数据结构都可以被认为是一个objc_object.

最重要的特性就是,你可以向OC中的任何对象发送消息

运行原理就是,当你向一个OC对象发送消息时,运行时库会根据对象的isa指针找到这个对象所属的类.这个类会包含一个所有实例方法的列表及一个指向superclass的指针以便可以找到父类的实例方法。运行时库会在类的方法列表以及父类(们)的方法列表中寻找符合这个selector的方法,找到后即运行这个方法。关键点就是类要定义这个你发送给对象的消息。  

2、类在Runtime中的结构

struct objc_class 
{
    Class isa  OBJC_ISA_AVAILABILITY;
    //isa指针
    //实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
     //父类

    const char *name                                         OBJC2_UNAVAILABLE;
    //类名

    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    //成员变量列表

    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    //方法列表

    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    //方法缓存列表
    //调用过的方法存入缓存列表,下次调用先找缓存

    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
   //协议列表

#endif

} OBJC2_UNAVAILABLE;

 从上图可以知道Person类本身其实也是一个对象(p对象的isa指针指向Person类,Person类的isa指针指向它的元类meta class)

 

2、方法的调用分为类方法实例方法

2.1实例方法的调用过程( 当你向一个对象发送消息时)

   1.)在对象所属类的<缓存方法列表> 中去找要调用的方法,找到直接执行其实现。
   2.)对象所属类的的<缓存方法列表> 里没找到,就去<类的方法列表>里找,找到了就执行其实现。
   3.)还没找到,说明这个类自己没有了,就会通过super_class去向其父类里执行1、2。
   4.)如果找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,可以自己在拦截调用方法里面做一些处理。
   5.)如果没有在拦截调用里做处理,那么就会报错崩溃。

2.2类方法的调用过程( 当你向一个类发送消息时)

   1.)在元类meta class的<缓存方法列表> 中去找要调用的方法,找到直接执行其实现。
   2.)在元类meta class<缓存方法列表> 里没找到,就去<元类的方法列表>里找,找到了就执行其实现。
   3.)还没找到,说明这个类自己没有了,就会通过isa去meta类的父类里执行1、2。
   4.)如果找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,可以自己在拦截调用方法里面做一些处理。
   5.)如果没有在拦截调用里做处理,那么就会报错崩溃。

3、这里简单讲一下meta class元类

meta-class,就像Class一样,也是一个对象。你依旧可以向它发送消息调用函数,自然的,meta-class也会有一个isa指针指向其所属类。所有的meta-class使用基类的meta-class作为他们的所属类。具体而言,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己所属的类。
根据这个规则,所有的meta-class使用基类的meta-class作为它们的类,而基类的meta-class也是属于它自己,也就是说基类的meta-class的isa指针指向它自己。(译:完美的闭环)

4、动态方法解析

当本类包括父类直到基类cache包括方法列表class_rw_t中都找不到方法时,就会进入动态方法解析阶段;
动态解析对象方法时,会调用+(BOOL)resolveInstanceMethod:(SEL)sel方法。
动态解析类方法时,会调用+(BOOL)resolveClassMethod:(SEL)sel方法。

5、消息快速转发

当本类没有实现方法,并且没有动态解析方法,就会调用forwardingTargetForSelector函数,进行消息转发,我们可以实现forwardingTargetForSelector函数,在其内部将消息转发给可以实现此方法的对象。
如果forwardingTargetForSelector函数返回为nil或者没有实现的话,就会调用methodSignatureForSelector方法,用来返回一个方法签名,这也是我们正确跳转方法的最后机会。
如果methodSignatureForSelector方法返回正确的方法签名就会调用forwardInvocation方法,forwardInvocation方法内提供一个NSInvocation类型的参数,NSInvocation封装了一个方法的调用,包括方法的调用者,方法名,以及方法的参数。在forwardInvocation函数内修改方法调用对象即可。
如果methodSignatureForSelector返回的为nil,就会来到doseNotRecognizeSelector:方法内部,程序crash提示无法识别选择器unrecognized selector sent to instance。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐