<span id='9ymrn'></span>
<fieldset id='9ymrn'></fieldset>

    <dl id='9ymrn'></dl>

      <code id='9ymrn'><strong id='9ymrn'></strong></code>

        <i id='9ymrn'></i>

          <ins id='9ymrn'></ins>

        1. <tr id='9ymrn'><strong id='9ymrn'></strong><small id='9ymrn'></small><button id='9ymrn'></button><li id='9ymrn'><noscript id='9ymrn'><big id='9ymrn'></big><dt id='9ymrn'></dt></noscript></li></tr><ol id='9ymrn'><table id='9ymrn'><blockquote id='9ymrn'><tbody id='9ymrn'></tbody></blockquote></table></ol><u id='9ymrn'></u><kbd id='9ymrn'><kbd id='9ymrn'></kbd></kbd>

          <i id='9ymrn'><div id='9ymrn'><ins id='9ymrn'></ins></div></i>
          <acronym id='9ymrn'><em id='9ymrn'></em><td id='9ymrn'><div id='9ymrn'></div></td></acronym><address id='9ymrn'><big id='9ymrn'><big id='9ymrn'></big><legend id='9ymrn'></legend></big></address>
        2. iOS源码解析:runtime<一> isa,class底层结构窥探

          • 时间:
          • 浏览:12
          • 来源:124软件资讯网

            isa详解

            要想学习runtime  ,首先要相识它底层的一些常用的数据结构 ,好比isa指针  。
            在arm64架构之前 ,isa就是一个通俗的指针  ,存储着Class  ,Meta-Class工具的内存地址
            从arm64架构最先  ,对isa举行了优化  ,酿成了一个共用体(union)结构  ,还使用位域来存储更多的信息 。
            我们打开runtime的源码  ,搜索objc_object {  ,我们在objc-private.h文件中找到了objc_object的结构:

            struct objc_object {
            private:
                isa_t isa;

            我们看到  ,这个isa并不是一个简朴的指针  ,它现在是isa_t类型的 。我们点进这个isa_t看看

            union isa_t 
            {
                isa_t() { }
                isa_t(uintptr_t value) : bits(value) { }

                Class cls;
                uintptr_t bits;


            #   define ISA_MASK        0x0000000ffffffff8ULL
            #   define ISA_MAGIC_MASK  0x000003f000000001ULL
            #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
                struct {
                    uintptr_t nonpointer        : 1;
                    uintptr_t has_assoc         : 1;
                    uintptr_t has_cxx_dtor      : 1;
                    uintptr_t shiftcls          : 33// MACH_VM_MAX_ADDRESS 0x1000000000
                    uintptr_t magic             : 6;
                    uintptr_t weakly_referenced : 1;
                    uintptr_t deallocating      : 1;
                    uintptr_t has_sidetable_rc  : 1;
                    uintptr_t extra_rc          : 19;
            #       define RC_ONE   (1ULL<<45)
            #       define RC_HALF  (1ULL<<18)
                };
            };

            我们可以看到  ,在arm64的结构下  ,isa的结构变得很是的庞大 ,首先我们要知道的是这是一个共用体 。nonpointer  ,has_assoc等后面的数字表现它们占用的位数  。

            下面我们从侧面去学习一下isa的这个结构  。

            Person类有三个BOOL类型的属性tall  ,rich  ,handsome  。

            @interface Person : NSObject

            @property (nonatomicassigngetter=isTall)BOOL tall;
            @property (nonatomicassigngetter=isRich)BOOL rich;
            @property (nonatomicassigngetter=isHandsome)BOOL handsome;
            @end

            可以像下面这样读取:

            Person *person = [[Person alloc] init];
                    person.tall = NO;
                    person.rich = YES;
                    person.handsome = YES;

                    NSLog(@"%d, %d, %d", person.isTall, person.isRich, person.isHandsome);

            我们还可以打印一下看person工具需要分配的内存空间:

            NSLog(@"%zd", class_getInstanceSize([person class]));

            打印出来的效果是16

            16是怎么得来的呢  ?isa指针占8个字节  ,然后三个BOOL类型的成员变量总共占三个字节  ,又由于内存对齐  ,以是总共占16个字节 。
            可是有一个问题  ,BOOL值原来只有YES或者NO两种可能  ,实在用一个字节去表现都有些铺张  ,用一位去表现都可以了  ,那么接下来我们就试着用一位去表现这些BOOL类型的成员变量  。
            既然每个BOOL变量都只用一位去表现 ,那就不能声明属性了  ,由于声明属性了 ,就会有成员变量 ,每个变量占用的内存不行能是一位  。

            我们就要自己去实现每个属性的set和get要领  。基本的头脑是建立一个char类型的成员变量_tallRichHandsome,既然是char类型  ,那么这个成员变量就占一个字节 ,即8位  ,我们用最后一位来表现rich这个属性  ,用倒数第二位来表现tall这个属性  ,用倒数第三位来表现handsome这个属性 。当这一位是0时表现属性值为NO ,为1时表现属性值为YES  。

            取值时  ,我们用_tallRichHandsome的值和0b0000 0001也就是1相与  ,可以取到其最后一位  ,和0b0000 0010也就是2相与 ,可以取到倒数第二位  ,和0b0000 0100即4相与  ,可以取到倒数第三位  。

            设值时  ,若是要把属性值设为YES ,也即是把_tallRichHandsome相对应的位置为1  ,那么把0b0000 0001和_tallRichHandsome相或  ,就可以把_tallRichHandsome的最后一位置为1 ,把0b0000 0010与之相或  ,可把倒数第二位置为1  ,把0b0000 0100与之相或  ,可把倒数第三位置为1 。而若是要把属性值设为NO  ,也即是把_tallRichHandsome的响应的位置为0  ,这个时间可以把0b0000 0001取反  ,然后和_tallRichHandsome相与  ,这样就把_tallRichHandsome的最后一位置为0 。

            //Person.m
            #define RichMask (1 << 0)    //也就是把0b0000 0001左移0位  ,照旧0b0000 0001
            #define TallMask (1 << 1)    //也就是把0b0000 0001左移1位  ,获得0b0000 0010
            #define HandsomeMask (1 << 2)//也就是把0b0000 0001左移2位  ,获得0b0000 0100

            @interface Person(){

                char _tallRichHandsome; //0b0000 0011
            }

            @end

            @implementation Person

            - (instancetype)init{

                if(self = [super init]){

                    _tallRichHandsome = 0b00000011;
                }
                return self;
            }


            - (void)setRich:(BOOL)rich{

                if (rich) {
                    _tallRichHandsome |= RichMask;
                }else{
                    _tallRichHandsome &= ~RichMask;
                }
            }

            - (void)setTall:(BOOL)tall{

                if (tall) {
                    _tallRichHandsome |= TallMask;
                }else{
                    _tallRichHandsome &= ~TallMask;
                }
            }

            - (void)setHandsome:(BOOL)handsome{

                if (handsome) {
                    _tallRichHandsome |= HandsomeMask;
                }else{
                    _tallRichHandsome &= ~HandsomeMask;
                }
            }

            - (BOOL)isRich{

                return !!(_tallRichHandsome & RichMask);
            }

            - (BOOL)isTall{

                return !!(_tallRichHandsome & TallMask);
            }

            - (BOOL)isHandsome{

                return !!(_tallRichHandsome & HandsomeMask);
            }
            @end

            当我们要设值和取值的时间可以:

            Person *person = [[Person alloc] init];
                    person.rich = YES;
                    person.tall = NO;
                    person.handsome = YES;
                    NSLog(@"tall: %d,rich: %d,handsome: %d", person.isRich, person.isTall, person.isHandsome);

            这样我们就完成了使用一个字节的数据来表现三个布尔类型的属性值  。

            使用结构体取代成员变量

            可是我们能发现  ,这种方式照旧很是的庞大冗长  ,那么有没有简练一些的要领呢 ?这个时间我们想到了用结构体来表现_tallRichHandsome这个char类型的成员变量  ,然后设置三个char类型的成员变量  ,划分是rich  ,tall ,handsome ,然后我们使用位域划定每个成员变量只占1位

            struct {
                    char rich : 1;
                    char tall : 1;
                    char handsome : 1;

                } _tallRichHandsome;   //0b0000 0011 成员变量会凭据先后顺序 rich在最右边一位 tall在倒数第二位  ,handsome在倒数第三位

            这样_tallRichHandsome整个结构体就只需要占用一个字节 ,而且读取和设值很是简朴 ,以rich属性为例:

            - (void)setRich:(BOOL)rich{

                _tallRichHandsome.rich = rich;
            }

            - (BOOL)isRich{

                return _tallRichHandsome.rich;
            }

            然后就能顺遂的取值和设值了  。

            使用配合体
                union Person{

                    int age;
                    int height;
                    char name;
                };

            这是一个名为Person的配合体  ,配合体和结构体很是相似  ,它们之间的差别之处在于  ,配合体中几个成员变量是共享统一块内存空间 ,什么意思呢 ?好比说这个配合体有三个成员变量age  ,height  ,name  ,若是这是一个机构体  ,那么这个结构体就占9个字节  ,这三个成员变量之间的内存时互不干预干与的  。而若是这是配合体 ,则由于三个成员变量中占内存最大的是占四个字节 ,以是这个配合体是占四个字节 ,age  ,height ,name都是存储在这四个字节中 ,若是先设置了age的值  ,然后取height的值 ,那么取到的就是刚刚设置的age的值  。

            诠释了配合体的观点之后我们再来看看下面这个配合体:

            union {
                    char bits;
                    struct {
                        char rich : 1;
                        char tall : 1;
                        char handsome : 1;
                    } ;   
                }_tallRichHandsome;

            这个配合体中有两个成员变量  ,一个是char类型的bits ,一个是一个结构体  。那么这个配合体需要内存分配几多空间呢?配合体占几多内存空间取决于占内存空间最大的谁人成员变量 ,这个bits和这个结构体都是占一个字节  ,以是结构体是占一个字节  。
            然后我们就可以使用这个配合体举行取值和赋值:

            - (void)setRich:(BOOL)rich{

                if (rich) {
                    _tallRichHandsome.bits |= RichMask;
                }else{
                    _tallRichHandsome.bits &= ~RichMask;
                }
            }

            - (void)setTall:(BOOL)tall{

                if (tall) {
                    _tallRichHandsome.bits |= TallMask;
                }else{
                    _tallRichHandsome.bits &= ~TallMask;
                }
            }

            我们可以看到  ,这实在和之前使用成员变量_tallRichHandsome险些是一样的  。配合体中的结构体基本是不起作用  ,我们在取值和赋值的时间都没有用到  ,它唯一的作用就是其诠释作用 ,让我们知道最右边那位代表rich  ,倒数第二位代表tall  ,倒数第三位代表handsome  。我们甚至可以把这个结构体删除都不影响  。

            再看isa的结构
            union isa_t 
            {
                isa_t() { }
                isa_t(uintptr_t value) : bits(value) { }

                Class cls;
                uintptr_t bits;


            #   define ISA_MASK        0x0000000ffffffff8ULL
            #   define ISA_MAGIC_MASK  0x000003f000000001ULL
            #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
                struct {
                    uintptr_t nonpointer        : 1;
                    uintptr_t has_assoc         : 1;
                    uintptr_t has_cxx_dtor      : 1;
                    uintptr_t shiftcls          : 33// MACH_VM_MAX_ADDRESS 0x1000000000
                    uintptr_t magic             : 6;
                    uintptr_t weakly_referenced : 1;
                    uintptr_t deallocating      : 1;
                    uintptr_t has_sidetable_rc  : 1;
                    uintptr_t extra_rc          : 19;
            #       define RC_ONE   (1ULL<<45)
            #       define RC_HALF  (1ULL<<18)
                };
            };

            再看到这个结构应该要熟悉许多了  。现在我们就知道  ,这个配合体的所有信息都存储在bits中  。ISA_MASK,ISA_MAGIC_MASK,ISA_MAGIC_VALUE都是掩码  ,用bits和这些掩码相与就可以获得nonpointer ,has_assoc  ,shiftcls等信息  。nonpointer  ,has_assoc  ,shiftcls也都接纳了位域  ,它们占用的位数都在后面标出了  。

            • shiftcls
              存储着Class  ,Meta-Class工具的内存地址

            • nonpointer
              0  ,代表通俗的指针  ,存储着Class  ,Meta-Class工具的内存地址
              1  ,代表优化过 ,使用位域存储更多的信息

            • has_assoc
              是否有设置过关联工具  ,若是没有  ,释放时会更快

            • magic
              用于在调试时分辨工具是否未完成初始化

            • weakly_referenced
              是否有被弱引用指向过 ,若是没有  ,释放时会更快

            • deallocating
              工具是否正在释放

            • extra_rc
              内里存储的值是引用计数器减1

            这内里比力主要的就是shiftcls这个成员变量  ,这个成员变量内里存放的就是Class ,Meta-Class的地址值  ,要取得这个值 ,需要bits & ISA_MASK ,我们把ISA_MASK睁开:0b0000000000000000000000000000111111111111111111111111111111111000  ,这内里有33个1 ,也就是取了bits中的33位  ,可是这样一来就有个问题 ,它们相与后的最后三位是一定为0  ,那么我们打印一下工具的地址来验证一下  。

            NSLog(@"%p\n %p\n", [Person class], object_getClass([Person class]));

            打印效果:

            0x10313cf58
            0x10313cf80

            一个末端是8  ,一个末端是0  ,由于都是16进制 ,以是后面都是音3个0末端  。

            class详解

            05A81BEA-1E16-4E40-BF03-395BB6DDAA7C.png

            这个图是实例工具  ,类工具  ,元类工具的一个结构图  ,从这个图中我们可以看出 ,类工具和元类工具的结构实在很是相似  ,不外是在要领内里  ,类工具存储的是工具要领  ,元类工具存储的是类要领  ,实在我们可以把元类工具看成是特殊的类工具 。

            class_rw_t结构

            我们在runtime的源码中搜索objc_class  ,然后在obj-runtime-new.h这找到了class的结构:

            struct objc_class : objc_object {
                // Class ISA;
                Class superclass;
                cache_t cache;             // 要领缓存
                class_data_bits_t bits;    // 用于获取类的详细信息

                class_rw_t *data() { 
                    return bits.data();
                }
            };

            通过对isa的学习  ,现在我们队bits应该有了更多的熟悉  ,它包罗着许多的信息 。通过bits & FAST_DATA_MASK就可以获得class_rw_t这个结构体  。rw的意思是readwrite  ,即可读可写  。类的主要信息都是存储在class_rw_t中:

            struct class_rw_t {
                // Be warned that Symbolication knows the layout of this structure.
                uint32_t flags;
                uint32_t version;

                const class_ro_t *ro;

                method_array_t methods;    //要领列表
                property_array_t properties; //属性列表
                protocol_array_t protocols;   //协议列表

                Class firstSubclass;
                Class nextSiblingClass;

                char *demangledName;
            };

            在class_rw_t中可以存放要领列表  ,属性列表 ,协议列表等等  。另有一个指向class_ro_t这个结构体的指针ro  。再看一下class_ro_t的结构:

            struct class_ro_t {
                uint32_t flags;
                uint32_t instanceStart;
                uint32_t instanceSize;
            #ifdef __LP64__
                uint32_t reserved;
            #endif

                const uint8_t * ivarLayout;

                const char * name;   //类名
                method_list_t * baseMethodList;
                protocol_list_t * baseProtocols;
                const ivar_list_t * ivars;    //成员变量列表

                const uint8_t * weakIvarLayout;
                property_list_t *baseProperties;

                method_list_t *baseMethods() const {
                    return baseMethodList;
                }
            };

            ro的意思实在就是readonly  ,也就是只读  。
            以是总结一下  ,class的结构就是这样:

            BFBF37E8-B460-4A57-854D-F26A38BDFA08.png
            那这里就有一个疑问了 ,在class_rw_t中已经有了种种列表来装要领  ,属性和协议那为什么在class_ro_t中另有列表来装要领协议成员变量等等呢  ?缘故原由就是在_class_ro_t中存放的是类初始化时自己的要领  ,协议  ,成员变量等  ,是不包罗分类的 ,因此这些是不能改变的 ,只读的  。而class_rw_t中的要领列表  ,属性列表  ,协议列表等包罗的是该类自己加上分类的所有的要领  ,属性  ,协议  ,因此是可变的  ,即可写 。

            然后我们再看一下class_rw_t中的要领列表:

                method_array_t methods;
                property_array_t properties;
                protocol_array_t protocols;

            然后我们点进method_array_methods看看:

            class method_array_t : 
                public list_array_tt<method_tmethod_list_t
            {
                typedef list_array_tt<method_tmethod_list_t> Super;

            通过这个结构我们能看到method_array_t是一个二维数组  ,也就是第一层也是一个数组 ,每个数组元素是method_list_t ,这又是一个一维数组 ,这个一维数组中存放的每个元素是method_t  。
            property_array_t ,protocol_array_t都是这种结构 ,是二维数组  。
            也即是下图这种结构:

            D1B11435-F41F-4E17-8221-4C4D829CACA3.png

            我们再看一下class_ro_t中的要领类表:

                method_list_t * baseMethodList;
                protocol_list_t * baseProtocols;
                const ivar_list_t * ivars;

            我们知道 ,method_list_t就是一个一维数组 ,而这个一维数组内里装的就是method_t  ,即下图这种结构:

            A52B5508-89C6-4159-A10A-2E1CF2E7B4DA.png下面我们剖析一下class_ro_t和class_rw_t中数组类型差别的原理:
            我们知道 ,class_ro_t中存放的要领属性协议等属于类自己的 ,那这样我只需要一个一维数组来存放类的要领和属性 ,一个一维数组来存放协议  ,完全没须要用一个二维数组 。可是class_rw_t就纷歧样了  ,它会存放分类的要领  ,协议吗  ,属性  ,建立一个二维数组  ,将每个分类的要领放在一个数组中  ,最后一个数组中放类自己的要领 ,这样就很是科学利便 。

            528CC59C-2538-4E7E-90C1-5BBCDDCF0C2C.png上面这段代码可以说明class_rw__t和class_ro_t的先后顺序 。
            总结起来就是cls->data()先指向class_ro_t  ,这个时间还没有class_rw_t ,然后再给class_rw_t分配内存  ,让其ro指针指向class_ro_t  ,最后让cls->data()指向class_rw_t  。**

            method_t结构

            不管是class_rw_t照旧class_ro_t的要领列表  ,最小的单元一定是method_t这个结构 ,下面我们就一起来看一下这个结构:

            struct method_t {
                SEL name;//就是要领名
                const char *types;  //编码 (返回值类型 ,参数类型)
                IMP imp; //指向函数的指针 (函数地址)
            };

            method_t结构体中第一个成员是SEL类型的name ,这个name就是要领名  ,好比说要领是- (void)test; ,那name就是test  ,若是要领是- (void)test:(int)age  ,那name就是test:  。