美文网首页
聊聊Runtime的objc_mesgSend

聊聊Runtime的objc_mesgSend

作者: conowen | 来源:发表于2017-02-07 10:59 被阅读38次
     * author:conowen@大钟                                                                                                                          
     * E-mail:conowen@hotmail.com      
    

    1、前言

    上一篇博文简单聊了一下runtime的基本概念,能干嘛,通过一个小demo来简单说了一下runtime的用处,以下还是以这个demo来聊聊。

    - (void)viewDidLoad {  
        [super viewDidLoad];  
        // Do any additional setup after loading the view, typically from a nib.  
        NSString *str0 = [NSString stringWithFormat:@"%@",@"conowen"];  
        NSLog(@"length = %lu",[str0 length]);  
        NSString *str = ((NSString *(*)(id, SEL, NSString *, ...))(voidvoid *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"),@"%@", @"conowen");  
        NSLog(@"length = %lu",((NSUInteger (*)(id, SEL))(voidvoid *)objc_msgSend)((id)str, sel_registerName("length")));  
    }  
    

    2、objc_msgSend 函数详解

    objc_msgSend 函数原形为
    OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)//后面省略号表示若干个参数,可有可无
    上述的[str0 length]会被转换为objc_msgSend(str0,@selector(length));

    同样的还有以下:

    objc_msgSend_stret当发送的消息要返回的是结构体类型时

    objc_msgSend_fpret当发送的消息要返回的是浮点数时

    objc_msgSendSuper 如果要给超类(父类)发消息,就用这个函数处理

    同理,还有objc_msgSendSuper_stretobjc_msgSendSuper_fpret。通常来说,编译器会根据实际情况,动态地调用这个几个方法中的一个,还有需要明确一点的是,objc_msgSend这个函数并没有返回数据,而是调用相对应的方法所返回来的数据。

    3、objc_msgSend流程

    说流程之前,我们先来看看这个函数里面的参数是什么,消息接收者id是一个指向任意对象的类型,而选择子SEL可以理解指向任意方法的类型,同id。

    3.1、id

    在objc.h可以看到以下定义

    // A pointer to an instance of a class.  
    typedef struct objc_object *id;  
    // An opaque type that represents a method selector.  
    typedef struct objc_selector *SEL;  
    

    然后再查看objc_object的定义如下,表示这是一个类的对象,这个结构体中只包含一个Class类型的isa指针(isa的缩写是 is a,可以理解为:This object "objectA"is a object of Class A,这个对象是一个A类的对象。),所以根据这个isa指针就能找到这个对象所属的类。
    (引申:在objc里面,任何对象其实都是C语言里面的结构体)

    //Represents an instance of a class.  
    struct objc_object {  
        Class isa  OBJC_ISA_AVAILABILITY;  
    };  
    

    而Class的定义如下,之所以说isa是指针,是因为Class指向的是objc_class类型的指针。
    // An opaque type that represents an Objective-C class. typedef struct objc_class *Class;

    而objc_class的定义如下,其中!OBJC2表示非Object c 2.0的版本,Object c是从1.0发展到现在的2.0,这个声明是为了兼容1.0的Object c,若是Object c 2.0的话,可见objc_class与objc_object的定义是一样的,都是只有一个isa指针,所以说在Object c里面,类也是一个对象。关于Object c的类与对象关系,将在下一篇博文详细说明。

    struct objc_class {  
        Class isa  OBJC_ISA_AVAILABILITY;  
      
    #if !__OBJC2__  
        Class super_class                                        OBJC2_UNAVAILABLE;  
        const charchar *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;  
    /* Use `Class` instead of `struct objc_class *` */  
    

    虽然上面的定义包含了非Object c 2.0,但是我们还是可以详细说说,这个结构体包含的这些个玩意到底是啥。
    从上往下可包含了父类指针,类名,类的版本信息,类的信息,类的实例大小,类的成员变量,类的方法表,类的方法缓存表,还有类的协议。

    3.2、category 增加成员变量

    注意一点:可以看到这个结构体里面有包含instance_size这个变量,表示类的实例变量大小,当实例化一个类的时候,指的是开辟一块内存区域,这块内存区域包含了isa指针与类的所有成员变量,也就是说当实例化一个类时,内存已经布局好了,当然不允许你直接增加成员变量,虽然不能直接增加成员变量,但是可以通过以下方法添加:

    /**  
     * Sets an associated value for a given object using a given key and association policy. 
     *  
     * @param object The source object for the association. 
     * @param key The key for the association. 
     * @param value The value to associate with the key key for object. Pass nil to clear an existing association. 
     * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.” 
     *  
     * @see objc_setAssociatedObject 
     * @see objc_removeAssociatedObjects 
     */  
    OBJC_EXPORT void objc_setAssociatedObject(id object, const voidvoid *key, id value, objc_AssociationPolicy policy)  
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);  
      
    /**  
     * Returns the value associated with a given object for a given key. 
     *  
     * @param object The source object for the association. 
     * @param key The key for the association. 
     *  
     * @return The value associated with the key \e key for \e object. 
     *  
     * @see objc_setAssociatedObject 
     */  
    OBJC_EXPORT id objc_getAssociatedObject(id object, const voidvoid *key)  
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);  
      
    /**  
     * Removes all associations for a given object. 
     *  
     * @param object An object that maintains associated objects. 
     *  
     * @note The main purpose of this function is to make it easy to return an object  
     *  to a "pristine state”. You should not use this function for general removal of 
     *  associations from objects, since it also removes associations that other clients 
     *  may have added to the object. Typically you should use \c objc_setAssociatedObject  
     *  with a nil value to clear an association. 
     *  
     * @see objc_setAssociatedObject 
     * @see objc_getAssociatedObject 
     */  
    OBJC_EXPORT void objc_removeAssociatedObjects(id object)  
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);  
    

    3.3、category 增加成员方法

    那为何又可以增加类方法呢(category),我们可以观察到struct objc_method_list **methodLists这是一个指向objc_method_list的二级指针(指向指针的指针,因为指针也是一个变量,本身也要存储在内存中。)这就为什么方法列表为什么能增加的原因,因为objc_class存储是方法列表地址的地址,而指向的这些方法所需要的内存空间并没有在rumtime就布局好了,所以就不担心category增加方法的时候会影响类实例的内存布局。
    附上objc_ivar_listobjc_method_list的定义

    struct objc_ivar {  
        charchar *ivar_name                                          OBJC2_UNAVAILABLE;  
        charchar *ivar_type                                          OBJC2_UNAVAILABLE;  
        int ivar_offset                                          OBJC2_UNAVAILABLE;  
    #ifdef __LP64__  
        int space                                                OBJC2_UNAVAILABLE;  
    #endif  
    }                                                            OBJC2_UNAVAILABLE;  
      
    struct objc_ivar_list {  
        int ivar_count                                           OBJC2_UNAVAILABLE;  
    #ifdef __LP64__  
        int space                                                OBJC2_UNAVAILABLE;  
    #endif  
        /* variable length structure */  
        struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;  
    }                                                            OBJC2_UNAVAILABLE;  
      
      
    struct objc_method {  
        SEL method_name                                          OBJC2_UNAVAILABLE;  
        charchar *method_types                                       OBJC2_UNAVAILABLE;  
        IMP method_imp                                           OBJC2_UNAVAILABLE;  
    }                                                            OBJC2_UNAVAILABLE;  
      
    struct objc_method_list {  
        struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;  
      
        int method_count                                         OBJC2_UNAVAILABLE;  
    #ifdef __LP64__  
        int space                                                OBJC2_UNAVAILABLE;  
    #endif  
        /* variable length structure */  
        struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;  
    }   
    

    3.4、IMP是什么

    以上主要的可以关注objc_method结构体里面的IMP类型,在objc.h头文件中定义如下

    /// A pointer to the function of a method implementation.   
    #if !OBJC_OLD_DISPATCH_PROTOTYPES  
    typedef void (*IMP)(void /* id, SEL, ... */ );   
    #else  
    typedef id (*IMP)(id, SEL, ...);   
    #endif 
    

    简单来说,IMP(implement)是一个函数指针,指向了方法的具体实现。实际的应用场景是这样的,当你写了Object c代码,如[NSString alloc]的时候,在运行时转换为objc_mesgSend函数形式,最终这个alloc消息最终会执行什么代码,是有IMP来决定的,IMP直接指向了方法的实现。这样就会很有趣了,应该可以直接操作方法的入口,那不就是可以更改替换系统方法,这种技术被称为“方法调配”(method swizzling)。原理就是让IMP指针本来指向改到其他方法实现那里去。

    3.5、指针函数与函数指针

    引申:指针函数与函数指针的区别

    • a、指针函数
      表示函数返回值是一个指针类型,定义如下
     //类型标识符  *函数名(参数表)  
    intint *fun(x,y); 
    
    • b、函数指针
      表示指向这个函数的指针变量,定义如下
    类型标识符  (*函数名)(参数表)  
    int (*fun) (int x,y); //函数名前面有一个星号,然后用小括号包起来  
    fun=funTest; /* 将funTest函数的首地址赋给指针  
    

    3.6、objc_class的cache是什么

    我们在rumtime.h头文件可以看到它的定义如下

    struct objc_cache {  
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;  
        unsigned int occupied                                    OBJC2_UNAVAILABLE;  
        Method buckets[1]                                        OBJC2_UNAVAILABLE;  
    };  
    

    如果知道CPU的L1 cache和L2 cache,就大概知道这玩意设计有什么作用了。摘自某度百科的解说

    “L1 cache(一级缓存)主要将CPU的硬指令长期存储,以便CPU在调用指令时不必再通过与内存交换数据来取得,另外,还将最近处理的进程数据(中间数据)存放在一级缓存;而L2 cache(二级缓存)则是完全存放最近处理的进程数据(中间数据)和即将调用的数据。通过这样一来设置,就可以避免CPU运算过程中要频繁与内存交换数据,减少CPU的等待时间,提高CPU的利用效率。”

    没错,objc_class结构体里面的cache就是这样的作用,每次消息接收者接收到消息的时候,都是先在cache里面查找是否有对应的方法,而不是直接到isa指向类的方法列表里面查找,这样会导致效率太低。同时,rumtime系统会把最近执行的方法存储在cache里面去(若是cache里面没有存储到的话)。

    4、objc_mesgSend的调用流程

    • 1、首先要明白,这是实质上是对一个对象发送消息,首先会根据这个对象的isa指针,找到这个对象是属于哪个类。
    • 2、找到类之后,现在类结构体里面的cache里面查找这个消息对应IMP指针(找到就直接跳到IMP指针指向的方法,然后执行)
    • 3、若是在cache找不到就会到该类的方法列表里面找。
    • 4、若是在该类的方法列表找不到,就通过super_class指针,在父类的方法列表里面找,一直往上,根据继承关系寻找。
    • 5、若是找到NSObject还找不到,就检查是否有动态创建方法体处理这个消息,如果还是没有就执行“消息转发”(message forwarding)操作。

    元类MetaClass

    从上面可以知道,对一个实例对象发送消息,首先会根据这个对象的isa指针,找到这个对象是属于哪个类。我们发现 Class 本身也有一个 isa 指针,指向的是它的 MetaClass。

    • 当我们对一个实例发送消息时(-开头的方法),实际上,通过这个实例对象的isa指针找到所属的Class,然后在对应的类的 methodLists 里查找方法。
    • 当我们对一个类发送消息时(+开头的方法),实际上会通过这个Class的isa指针,找到类的MetaClass,然后再在MetaClass的 methodLists 里查找方法。也就说说,元类存储的是类方法。
      这一过程如下图所示:
    • 每个 Class 都有一个 isa 指针指向一个唯一的 Meta Class
    • 每一个 Meta Class 的 isa 指针都是直接指向最上层基类的 Meta Class,即 NSObject 的 MetaClass,而最上层基类的 MetaClass 的 isa 指针又指向自己。
    • 可以理解Class其实也是对象,实例对象和Class都是id类型的对象。
    image.png

    参考文章

    https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runtime.html

    相关文章

      网友评论

          本文标题:聊聊Runtime的objc_mesgSend

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