• Objective-C 方法交换实践(一)


    一、Objective-C 中的基本类型

    首先看下 Objective-C 的对象模型,每个 Objective-C 对象都是一个指向 Class 的指针。Class 的结构如下:

    struct objc_class {
    	Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
    	Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    	const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    	long version                                             OBJC2_UNAVAILABLE;
    	long info                                                OBJC2_UNAVAILABLE;
    	long instance_size                                       OBJC2_UNAVAILABLE;
    	struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    	struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    	struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    	struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    这个结构已经有很多的说明了,下面简单的再描述下

    1. 变量列表

    变量 Ivar 也是一个结构体,每个 Class 中用变长结构体的方式存储了 Class 的变量列表。 IVar 的定义如下,包含 名称、类型、偏移、占用空间。

    typedef struct objc_ivar *Ivar;
    
    struct objc_ivar {
    	char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    	char * _Nullable 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;
    

    2. 方法列表

    每个方法 Method 的定义如下,包含 SEL 指向对外的命名,char * 型 的方法类型, IMP 方法指针,指向具体的函数实现。

    typedef struct objc_method *Method;
    
    struct objc_method {
        SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
        char * _Nullable method_types                            OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    同样一个变长结构体来存储方法列表。Class 中的这个列表是个2级指针,所以可以向 Class 中动态的添加方法。

    struct objc_method_list {
        struct objc_method_list * _Nullable 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;
    }                                                            OBJC2_UNAVAILABLE;
    

    3. 缓存

    同样一个变长结构体存储之前找到的 Method。

    1)、mask:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1;
    2)、occupied:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目。

    他是通过 要查找的 Method 的 SEL 地址和 mask 做一系列运算来确定 Method 的存储与查找位置。更详细的说明可以看参考4。其中提到的几点也在说下:
    子类的 cache 会存储在父类中找到的方法;cache 的大小会动态增加,但是增加之前一定会先清空自己(变长结构体的特性)。

    typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
    
    #define CACHE_BUCKET_NAME(B)  ((B)->method_name)
    #define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
    #define CACHE_BUCKET_VALID(B) (B)
    #ifndef __LP64__
    #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
    #else
    #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
    #endif
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
    };
    

    4. 协议

    typedef struct objc_category *Category;
    
    struct objc_category {
        char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
        char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    
    
    struct objc_protocol_list {
        struct objc_protocol_list * _Nullable next;
        long count;
        __unsafe_unretained Protocol * _Nullable list[1];
    };
    

    5. isa 和 superClass

    看一张经典的图:

    isa 表明当前对象所属于的 Class 类型(Class 也是一个对象,Class 的类型叫 MetaClass)。
    superClass 表明当前对象从哪个父类派生出来的,根类型(比如 NSObject、NSProxy)的 superClass 是 nil。
    向对象发送消息时,会去方法列表里面查询,找不到会去父类的方法列表,再找不到会进入动态添加、消息转发、消息包装的过程。向 Class 发送消息时,会去 MetaClass 走同样的过程。

    二、self 和 super

    1. self 是类的隐藏的参数,指向当前调用方法的类

    2. super 是一个"编译器指示符", 是一个标记,告诉编译器起始于当前类的父类方法列表中搜索方法的实现。

    看一个例子

    @A
    - (void)show{
    }
    
    - (void)log {
        NSLog(@"i am a");
    }
    
    - (void)print {
        NSLog(@"i am %@",[self class]);
    }
    
    @end
    
    @B: A
    
    - (void)show
    {
        [self/super log];
        [self/super print];
    }
    
    - (void)log {
        NSLog(@"i am b");
    }
    
    - (void)print {
        NSLog(@"i am %@",[self class]);
    }
    
    @end
    
    @ C: B
    - (void)log {
        NSLog(@"i am c");
    }
    
    @end
    

    在 B 的show 方法中分别改成 self 和 super,如下调用会输出什么?

    C *c = [[C alloc] init];
    [c show];
    

    结果是 self 的时候 输出

    i am c
    i am C
    

    super 的时候输出

    i am a
    i am C
    

    用 self 调用方法,会编译成 objc_msgSend 方法,其定义如下:

    void objc_msgSend(void /* id self, SEL op, ... */ )
    

    第一个参数是消息接收者,也就是对象本身,第二个参数是调用的具体类方法的 selector。这里有个隐藏参数 _cmd,代表当前类方法的selector。

    用super 调用方法,会编译成 objc_msgSendSuper 方法,其定义如下:

    void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    

    其中 objc_super  的定义如下:

    /// Specifies the superclass of an instance. 
    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;
    
        /// Specifies the particular superclass of the instance to message. 
    #if !defined(__cplusplus)  &&  !__OBJC2__
        /* For compatibility with old objc-runtime.h header */
        __unsafe_unretained _Nonnull Class class;
    #else
        __unsafe_unretained _Nonnull Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    

    三、消息转发

    当向一个类的实例发送方法时,会去 Class 结构的方法缓存列表 objc_cache  和 方法列表 objc_method_list  中查找有没有这个方法,如果没有的话,则会进入消息转发阶段。
    消息转发主要分为两大阶段:

    1. 动态方法解析:看对象所属类是否能动态添加方法

    2. 转发阶段:既然第一步已经不会新增方法来响应,那系统就会请接受者看看有没有其他对象响应这个消息;如果没有,就把消息封装到 NSInvocation中,再做一次尝试。

    参考:
    1.http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
    2.http://time-track.cn/variable-length-structure.html
    3.https://tech.meituan.com/DiveIntoMethodCache.html
    4.http://blog.csdn.net/datacloud/article/details/7275170
    5.http://blog.csdn.net/wzzvictory/article/details/8487111

  • 相关阅读:
    MySQL概述
    Seleniumselenium基础入门
    MySQL数据库的安装与使用
    元素定位_id
    Selenium浏览器的前进、后退、刷新
    元素定位_tag_name
    Selenium浏览器操作_窗口大小设置
    元素定位_name
    搭建maven服务器(repository)
    使用dos命令生成目录树
  • 原文地址:https://www.cnblogs.com/v2m_/p/7868905.html
Copyright © 2020-2023  润新知