美文网首页iOS底层收集
iOS进阶-04类的结构

iOS进阶-04类的结构

作者: ricefun | 来源:发表于2020-02-05 19:36 被阅读0次
    初观类的结构

    先看源码:
    NSObjectd定义

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    

    Class定义

    typedef struct objc_class *Class;
    

    objc_class定义

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    
        void setInfo(uint32_t set) {
            assert(isFuture()  ||  isRealized());
            data()->setFlags(set);
        }
        ...
        ...
        ...
    

    objc_object定义

    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    上述源码总结:

    • Class是objc_class结构体的指针(注意看*)
    • objc_class 又是继承于objc_object的结构体
    • objc_object结构体中只有isa 一个属性
    类的内部结构

    先上调试代码:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject {
        NSString *hobby;//成员变量
    }
    @property (nonatomic,copy) NSString *nickName;//属性
    - (void)eat;
    + (void)sleep;
    
    @end
    NS_ASSUME_NONNULL_END
    #import "Person.h"
    @implementation Person
    - (void)eat {    
    }
    
    + (void)sleep {
    }
    @end
    
    #####
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"hello world!");
        }
        return 0;
    }
    

    在NSLog 打断点进行LLDB调试,获取class_data_bits_t bits内存情况:
    1.打印当前Person类的内存情况

    (lldb) x/4gx Person.class
    0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
    0x1000026e0: 0x00000001003db270 0x0000000000000000
    

    我们已经知道0x001d8001000026a9 是isa,又根据下面objc_class 源码可知:0x0000000100b38140 是superclass,
    0x00000001003db270是cache,但是bits我们不知道,反正不可能是0x0000000000000000

    //objc_class 源码
    struct objc_class : objc_object {
        // Class ISA; //8个字节
        Class superclass;//8个字节
        cache_t cache; //16个字节
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        ...
        ...
        ...
    }
    

    如何获取到class_data_bits_t bits?我在上面objc_class源码上标ISA 、superclass、cache的字节占用大小三者加起来刚好是32个字节,换成16进制就是20;所以bits就是0x1000026d0偏移20字节位0x1000026f0。所以继续LLDB调试:

    为什么是8、8、16,我在OC底层原理探索-03中放过一张数据类型占用字节大小的图,请自行查看

    (lldb) p (class_data_bits_t *)0x1000026f0//需要使用class_data_bits_t *)进行强转
    (class_data_bits_t *) $1 = 0x00000001000026f0
    (lldb) p $1->data()//根据源码方法 bits.data();获取class_rw_t,就是下面$3
    (class_rw_t *) $3 = 0x0000000102d2a230
    (lldb) p *$3//打印class_rw_t的内部结构
    (class_rw_t) $4 = {
      flags = 2148139008
      version = 0
      ro = 0x00000001000024e8
      methods = {
        list_array_tt<method_t, method_list_t> = {
           = {
            list = 0x0000000100002420
            arrayAndFlag = 4294976544
          }
        }
      }
      properties = {
        list_array_tt<property_t, property_list_t> = {
           = {
            list = 0x00000001000024d0
            arrayAndFlag = 4294976720
          }
        }
      }
      protocols = {
        list_array_tt<unsigned long, protocol_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
      demangledName = 0x0000000000000000
    }
    
    //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_rw_t数据我们已经拿到了;既然能拿到class_rw_t,其内部的class_ro_t是不是也能拿到。继续LLDB调试:

    (lldb) p $4.ro
    (const class_ro_t *) $6 = 0x00000001000024e8
    (lldb) p $6
    (const class_ro_t *) $6 = 0x00000001000024e8
    (lldb) p *$6
    (const class_ro_t) $7 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100001dd3 "\x02"
      name = 0x0000000100001dcc "Person"
      baseMethodList = 0x0000000100002420
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100002488
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000024d0
    }
    
    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;
        }
    };
    

    对比 class_ro_t源码,是不是同样的相似,说明class_ro_t数据我们已经拿到了;同样我们可以打印其内部的baseProtocols 、ivars 、baseMethodList,继续LLDB调试:

    (lldb) p $7.baseProperties
    (property_list_t *const) $8 = 0x00000001000024d0
    (lldb) p *$8//打印属性list
    (property_list_t) $9 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1//只有一个属性,即nickName
        first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      }
    }
    (lldb) p $9.get(0)//打印第一个属性,存在
    (property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
    (lldb) p $9.get(1) //打印第二个属性,就报错了
    Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    (lldb) p $7.ivars
    (const ivar_list_t *const) $11 = 0x0000000100002488
    (lldb) p *$11 //打印ivars 成员变量数组
    (const ivar_list_t) $12 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2//有两个hobby和nickName
        first = {
          offset = 0x00000001000025f8
          name = 0x0000000100001e18 "hobby"
          type = 0x0000000100001eb5 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    (lldb) p $12.get(0)//打印第1个ivar
    (ivar_t) $13 = {
      offset = 0x00000001000025f8
      name = 0x0000000100001e18 "hobby"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $12.get(1)//打印第2个ivar
    (ivar_t) $14 = {
      offset = 0x0000000100002600
      name = 0x0000000100001e68 "_nickName"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $7.baseMethodList
    (method_list_t *const) $15 = 0x0000000100002420
    (lldb) p *$15//打印实例方法列表
    (method_list_t) $16 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4//有4个?分别是 eat、nickName的get/set方法,还有一个系统自带的cxx_destruct方法
        first = {
          name = "eat"
          types = 0x0000000100001e72 "v16@0:8"
          imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
        }
      }
    }
    (lldb) p $16.get(0)
    (method_t) $17 = {
      name = "eat"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
    }
    (lldb) p $16.get(1)
    (method_t) $18 = {
      name = ".cxx_destruct"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
    }
    (lldb) p $16.get(2)
    (method_t) $19 = {
      name = "setNickName:"
      types = 0x0000000100001e82 "v24@0:8@16"
      imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
    }
    (lldb) p $16.get(3)
    (method_t) $20 = {
      name = "nickName"
      types = 0x0000000100001e7a "@16@0:8"
      imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
    }
    (lldb) p $16.get(4)//打印第5个就报错了
    Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    (lldb) 
    

    到这里,我想各位已经对Class的结构有了初步的认识了
    总结

    • objc_class中的含有class_data_bits_t bits
    • class_data_bits_t bits 通过bits.data();方法获取class_rw_t
    • class_rw_t含有class_ro_t
    • 成员变量存在class_ro_t中的ivars里面
    • 属性存在class_ro_t中的baseProperties里面,并且在ivars中也有一份带下滑划线的成员变量,并且会自动生成getter/set方法存在baseMethodList
    • 实例方法存类里面,在class_ro_t中的baseMethodList里面,且包含属性的getter/set方法还有系统实例方法.cxx_destruc
    • 类方法存在元类的里面(这个暂时不讲了,探索方法其实例方法一样)
    完整LLDB调试过程如下
    (lldb) x/4gx Person.class
    0x1000026d0: 0x001d8001000026a9 0x0000000100b38140
    0x1000026e0: 0x00000001003db270 0x0000000000000000
    (lldb) p (class_data_bits_t *)0x1000026f0
    (class_data_bits_t *) $1 = 0x00000001000026f0
    (lldb) p $1->data()
    (class_rw_t *) $3 = 0x0000000102d2a230
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2148139008
      version = 0
      ro = 0x00000001000024e8
      methods = {
        list_array_tt<method_t, method_list_t> = {
           = {
            list = 0x0000000100002420
            arrayAndFlag = 4294976544
          }
        }
      }
      properties = {
        list_array_tt<property_t, property_list_t> = {
           = {
            list = 0x00000001000024d0
            arrayAndFlag = 4294976720
          }
        }
      }
      protocols = {
        list_array_tt<unsigned long, protocol_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
      demangledName = 0x0000000000000000
    }
    (lldb) p $4.ro
    (const class_ro_t *) $6 = 0x00000001000024e8
    (lldb) p *$6
    (const class_ro_t) $7 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100001dd3 "\x02"
      name = 0x0000000100001dcc "Person"
      baseMethodList = 0x0000000100002420
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100002488
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000024d0
    }
    (lldb) p $7.baseProperties
    (property_list_t *const) $8 = 0x00000001000024d0
    (lldb) p *$8
    (property_list_t) $9 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1
        first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      }
    }
    (lldb) p $9.get[0]
    error: reference to non-static member function must be called
    (lldb) p $9.get(0)
    (property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
    (lldb) p $9.get(1) 
    Assertion failed: (i < count), function get, file /Users/Desktop/码/runtime/objc-runtime-new.h, line 117.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    (lldb) p $7.ivars
    (const ivar_list_t *const) $11 = 0x0000000100002488
    (lldb) p *$11 
    (const ivar_list_t) $12 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x00000001000025f8
          name = 0x0000000100001e18 "hobby"
          type = 0x0000000100001eb5 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    (lldb) p $12.get(0)
    (ivar_t) $13 = {
      offset = 0x00000001000025f8
      name = 0x0000000100001e18 "hobby"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $12.get(1)
    (ivar_t) $14 = {
      offset = 0x0000000100002600
      name = 0x0000000100001e68 "_nickName"
      type = 0x0000000100001eb5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $7.baseMethodList
    (method_list_t *const) $15 = 0x0000000100002420
    (lldb) p *$15
    (method_list_t) $16 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "eat"
          types = 0x0000000100001e72 "v16@0:8"
          imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
        }
      }
    }
    (lldb) p $16.get(0)
    (method_t) $17 = {
      name = "eat"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001c50 (LGTest`-[Person eat] at Person.m:12)
    }
    (lldb) p $16.get(1)
    (method_t) $18 = {
      name = ".cxx_destruct"
      types = 0x0000000100001e72 "v16@0:8"
      imp = 0x0000000100001ce0 (LGTest`-[Person .cxx_destruct] at Person.m:10)
    }
    (lldb) p $16.get(2)
    (method_t) $19 = {
      name = "setNickName:"
      types = 0x0000000100001e82 "v24@0:8@16"
      imp = 0x0000000100001ca0 (LGTest`-[Person setNickName:] at Person.h:15)
    }
    (lldb) p $16.get(3)
    (method_t) $20 = {
      name = "nickName"
      types = 0x0000000100001e7a "@16@0:8"
      imp = 0x0000000100001c70 (LGTest`-[Person nickName] at Person.h:15)
    }
    (lldb) p $16.get(4)
    Assertion failed: (i < count), function get, file /Users/Desktop/runtime/objc-runtime-new.h, line 117.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    
    类的结构图解

    相关文章

      网友评论

        本文标题:iOS进阶-04类的结构

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