美文网首页
ISA指向、类结构

ISA指向、类结构

作者: spades_K | 来源:发表于2020-10-26 10:15 被阅读0次

ISA指向、类结构

1.ISA指向

上次在对象本质和ISA指针学习中分析了对象的ISA指针指向当前的类对象,那么类对象的ISA指针指向什么,OC中整体的ISA指针指向是什么样的呢?这篇文章来学习下。

创建LGPerson类,继承于NSObject

NSObject *objc1 = [NSObject alloc];
LGPerson *objc2 = [LGPerson alloc];
// 获取metalClass
Class a = objc_getMetaClass("LGPerson");
  • 开始调试
(lldb) po objc1
<NSObject: 0x1006cc3e0>

(lldb) x/4gx 0x1006cc3e0
0x1006cc3e0: 0x001d80010034c141 0x0000000000000000
0x1006cc3f0: 0x726573554b575b2d 0x43746e65746e6f43
(lldb) p 0x001d80010034c141 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 4298424640
(lldb) po $1
NSObject

对象objc1isa指针0x001d80010034c141,经过计算得到指向了NSObject类,为Class类型。

// Class 声明
typedef struct objc_class *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
    .....
}
// objc_class 父类
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
...
}

objc_class中的 ISA是从objc_object中继承过来的,接下来打印$1内部储存结构继续调试。

(lldb) x $1
0x10034c140: f0 c0 34 00 01 00 00 00 00 00 00 00 00 00 00 00  ..4.............
0x10034c150: 30 0f 6d 00 01 00 00 00 07 00 00 00 10 80 04 00  0.m.............
(lldb) x/4gx 0x10034c140
0x10034c140: 0x000000010034c0f0 0x0000000000000000
0x10034c150: 0x00000001006d0f30 0x0004801000000007

(lldb) po 0x000000010034c0f0
NSObject

0x000000010034c0f08字节的Class,即为NSObject0x0000000000000000 通过objc_clss结构分析,应为superclass,即为nil

疑问 0x000000010034c0f0NSObjectISA,为什么打印出来还是NSObject

(lldb) x/4gx 0x000000010034c0f0
0x10034c0f0: 0x000000010034c0f0 0x000000010034c140
0x10034c100: 0x00000001006d1870 0x0004e03100000007
(lldb) po 0x000000010034c0f0
NSObject

(lldb) po 0x000000010034c140
NSObject

0x000000010034c0f0ISA又只向了自己,父类是0x000000010034c140 即是NSObject。问题来了,0x000000010034c0f00x000000010034c140到底哪个是我们经常用的NSObject

(lldb) p NSObject.class
(Class) $9 = NSObject
(lldb) x $9
0x10034c140: f0 c0 34 00 01 00 00 00 00 00 00 00 00 00 00 00  ..4.............
0x10034c150: 30 0f 6d 00 01 00 00 00 07 00 00 00 10 80 04 00  0.m.............
(lldb) po 0x10034c140
NSObject

(lldb) p objc_getMetaClass("NSObject")
(Class) $11 = 0x000000010034c0f0

初步总结

objc1NSObject对象,objc1中的isa指针指向NSObject类,NSObject类中的isa指针指向NSObject元类NSObject元类中的isa指针指向自己。NSObject元类继承于NSObject

初步总结图表

再次分析

(lldb) po objc2
<LGPerson: 0x1006cc130>

(lldb) p 0x001d8001000080f9 & 0x00007ffffffffff8ULL
(unsigned long long) $17 = 4295000312
(lldb) po $17
LGPerson

(lldb) x/4gx $17
0x1000080f8: 0x00000001000080d0 0x000000010034c140
0x100008108: 0x0000000100723a90 0x0004801000000007
(lldb) po 0x00000001000080d0
LGPerson

(lldb) po 0x000000010034c140
NSObject

(lldb) x/4gx 0x00000001000080d0
0x1000080d0: 0x000000010034c0f0 0x000000010034c0f0
0x1000080e0: 0x00000001006d19f0 0x0003e03100000007
(lldb) po 0x000000010034c0f0
NSObject

(lldb) p objc_getMetaClass("LGPerson")
(Class) $21 = 0x00000001000080d0

objc2对象中isa指向地址为0x1000080f8LGPerson类,LGPerson类中的isa指针指向地址为0x00000001000080d0LGPerson元类LGPerson元类isa指针指向地址为0x000000010034c0f0NSObject元类。后面的和我们前面验证的就一样了。

全面总结

苹果提供的走位图

isa流程图.png

全面总结

实例对象isa指针指向它的类对象,类对象的isa指针指向它的元类元类isa指针指向根元类根元类isa指针指向自己。
子类的元类继承自父类的元类,直到根元类,根元类继承自根类NSObject

2.类结构分析

内存偏移

        int p1 = 10;
        int p2 = 10;
        NSLog(@"%d -- %p", p1, &p1);
        NSLog(@"%d -- %p", p2, &p2);
        
        int c[4] = {1,2,3,4};
        int *d = c;
        NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
        NSLog(@"%p -- %p - %p", d, d+1, d+2);

打印结果

2020-10-23 18:15:05.761402+0800 KCObjc[21732:317675] 10 -- 0x7ffeefbff498
2020-10-23 18:15:05.762237+0800 KCObjc[21732:317675] 10 -- 0x7ffeefbff49c
2020-10-23 18:15:05.762297+0800 KCObjc[21732:317675] 0x7ffeefbff4a0 -- 0x7ffeefbff4a0 - 0x7ffeefbff4a4
2020-10-23 18:15:05.762352+0800 KCObjc[21732:317675] 0x7ffeefbff4a0 -- 0x7ffeefbff4a4 - 0x7ffeefbff4a8

0x7ffeefbff4980x7ffeefbff49cp1p2的指针地址,两个地址相差4字节,正好为 int类型占用的大小。
通过数组打印可知,数组c的首地址和第一个元素的地址相同,第一个元素和第二个元素地址相差4字节,即为int --(储存的类型)类型所占用的大小,也可用d指针 +1+2操作指向不同元素。

所以我们用内存偏移的方式获取类中的储存信息。
首先看下类结构的声明:参考objc-781源码

struct objc_class : objc_object {
    // Class ISA;    //8
    Class superclass;  //8
    cache_t cache;             // formerly cache pointer and vtable  // 8+4+2+2 = 16
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

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

经过寻找并没有找到相关的储存的方法列表属性列表的地方,但是发现class_rw_t *data()结构体中包含三个方法:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
.....
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
}

class_rw_t类型为bits.data()返回值,只要找到bits属性的首地址就可以进行调用了,根据内存偏移原则,只要找到对象的首地址+ISA(父类继承)占用大小+superclass属性占用大小+cache属性占用大小Class类型我们知道是占用8字节,只差cache属性了。

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 结构体指针类型为 8字节
    explicit_atomic<mask_t> _mask;               // uint32_t 类型为 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;  // typedef unsigned long           uintptr_t; 8字节

    mask_t _mask_unused;                         // uint32_t 类型为4字节

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;  // typedef unsigned long           uintptr_t; 8字节
    mask_t _mask_unused;  // uint32_t 类型为4字节
    /// 不在结构体中储存
    static constexpr uintptr_t maskShift = 48;
....
    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;   // short 2字节
#endif
    uint16_t _occupied; // short 2字节
.....
}

经过分析#if,#elif条件中属性都为12字节static声明的属性不会存在结构体中,剩下的 _flags_occupied属性都占用2字节,所以cache的占用大小为12+2+216总偏移量8+8+1632,接下来我们来验证,定义一个LGPerson类。

@interface LGPerson : NSObject
{
    NSInteger _age;
}
@property(nonatomic,copy) NSString *name;

-(void)run;
-(void)sing;
+(void)instance;
@end

寻找方法列表

(lldb) p LGPerson.class
(Class) $0 = LGPerson
(lldb) x/4gx $0  // 分4段16进制格式查看LGPerson内存分部
0x100008218: 0x00000001000081f0 0x000000010034c140
0x100008228: 0x0000000100346460 0x0000802400000000
(lldb) p 0x100008238
(long) $1 = 4295000632
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100008238
(lldb) p $2 ->data() // 调用 data()方法
(class_rw_t *) $3 = 0x0000000100659ee0
(lldb) p $3 ->methods() // 调用methods()方法
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000080f0
      arrayAndFlag = 4295000304
    }
  }
}
(lldb) p $4.list  //获取 list属性
(method_list_t *const) $5 = 0x00000001000080f0
(lldb) p *$5  // 获取 method_list_t对象内存
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 5
    first = {
      name = "sing"
      types = 0x0000000100003f7a "v16@0:8"
      imp = 0x0000000100003db0 (KCObjc`-[LGPerson sing])
    }
  }
}
(lldb) p $6.get(0)   // 根据下标获取对象
(method_t) $7 = {
  name = "sing"
  types = 0x0000000100003f7a "v16@0:8"
  imp = 0x0000000100003db0 (KCObjc`-[LGPerson sing])
}
(lldb) p $6.get(1)
(method_t) $8 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f7a "v16@0:8"
  imp = 0x0000000100003e20 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $6.get(2)
(method_t) $9 = {
  name = "name"
  types = 0x0000000100003f90 "@16@0:8"
  imp = 0x0000000100003dc0 (KCObjc`-[LGPerson name])
}
(lldb) p $6.get(3)
(method_t) $10 = {
  name = "setName:"
  types = 0x0000000100003f98 "v24@0:8@16"
  imp = 0x0000000100003df0 (KCObjc`-[LGPerson setName:])
}
(lldb) p $6.get(4)
(method_t) $11 = {
  name = "run"
  types = 0x0000000100003f7a "v16@0:8"
  imp = 0x0000000100003da0 (KCObjc`-[LGPerson run])
}
(lldb) p $6.get(5) // 越界 数组中有5个元素
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

从上面的分析得知:类对象实例方法储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->method_array_t类型 methods()-->method_list_t(数组)类型list属性中,问题是并没有找到类方法,问题保留一会分析。

寻找属性列表

(lldb) p $3 ->properties()  // $3 为class_rw_t类型
(const property_array_t) $21 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000081b8
      arrayAndFlag = 4295000504
    }
  }
}
(lldb) p $21.list // 获取list属性
(property_list_t *const) $22 = 0x00000001000081b8
(lldb) p *$22  // 打印list内存地址
(property_list_t) $23 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}
(lldb) p $23.get(0) // 获取数组元素
(property_t) $24 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $23.get(1)  // 越界 数组中只有一个元素
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

从上面的分析得知:对象属性储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->property_array_t类型 properties()-->property_list_t(数组)类型list属性中,只找到name成员,没有找到_age成员变量。

寻找成员变量

(lldb)  p $3 ->ro()
(const class_ro_t *) $25 = 0x00000001000080a8
(lldb) p *$25
(const class_ro_t) $26 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100003f3f "\x11"
  name = 0x0000000100003f36 "LGPerson"
  baseMethodList = 0x00000001000080f0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008170
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000081b8
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $26.ivars
(const ivar_list_t *const) $27 = 0x0000000100008170
(lldb) p *$27
(const ivar_list_t) $28 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000081e0
      name = 0x0000000100003f4a "_age"
      type = 0x0000000100003f82 "q"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p  $28.get(0)
(ivar_t) $29 = {
  offset = 0x00000001000081e0
  name = 0x0000000100003f4a "_age"
  type = 0x0000000100003f82 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p  $28.get(1)
(ivar_t) $30 = {
  offset = 0x00000001000081e8
  name = 0x0000000100003f4f "_name"
  type = 0x0000000100003f84 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p  $28.get(2)
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

从上面的分析得知:类对象成员变量储存在 objc_class结构体-->class_rw_t类型bits.data()方法--->class_ro_t类型 ro()-->ivar_list_t(数组)类型ivars属性中,包含_name_age成员变量。

寻找类方法

(lldb) x/4gx LGPerson.class
0x100008218: 0x00000001000081f0 0x000000010034c140
0x100008228: 0x0000000100346460 0x0000802400000000
(lldb) po 0x00000001000081f0  // 获取到元类
LGPerson

(lldb) p 0x00000001000081f0
(long) $2 = 4295000560
(lldb) x/4gx 0x00000001000081f0
0x1000081f0: 0x000000010034c0f0 0x000000010034c0f0
0x100008200: 0x00000001020168f0 0x0001e03500000007
(lldb) p (class_data_bits_t *)0x100008210   // 0x1000081f0 向后移动32字节。
(class_data_bits_t *) $3 = 0x0000000100008210
(lldb) p $3 ->data()
(class_rw_t *) $4 = 0x000000010064d0c0
(lldb) p $4 ->methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100008088
      arrayAndFlag = 4295000200
    }
  }
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100008088
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "instance"
      types = 0x0000000100003f7a "v16@0:8"
      imp = 0x0000000100003d90 (KCObjc`+[LGPerson instance])
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "instance"
  types = 0x0000000100003f7a "v16@0:8"
  imp = 0x0000000100003d90 (KCObjc`+[LGPerson instance])
}
(lldb) p $7.get(1)  //越界
Assertion failed: (i < count), function get, file /runtime/objc-runtime-new.h, line 439.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

从上面的分析得知:方法是保存在类对象的元类中的。

相关文章

  • isa指针指向和类结构分析

    isa指针指向和类结构分析 isa指向图 经典的isa指向图 从这张图能总结出类继承自父类,父类继承于NSObje...

  • objc 源码

    类的结构 关于isa的理解 由上图可知:实例变量的isa指向的是类,而类的isa指向的是元类(metaClass)...

  • ISA指向、类结构

    ISA指向、类结构 1.ISA指向 上次在对象本质和ISA指针[https://www.jianshu.com/p...

  • 元类(Meta Class)

    struct objc_object结构体实例它的isa指针指向类对象,类对象的isa指针指向了元类,super_...

  • isa的流程和类的继承

    isa的指向流程: NSObejct:实例对象的isa指向 元类,元类的isa指向根元类,根元类的isa指向自己 ...

  • iOS开发中方法查找流程图

    实例对象的isa指针指向类对象,类对象的isa指针指向元类,元类的isa指针指向根元类,根元类的isa指针指向自己...

  • 类的结构分析

    前言 书接上回isa结构分析,我们得知,对象的isa指针指向类,确切的说,是isa指针的shiftcls位域中指向...

  • iOS开发笔记-类和元类

    先说几个概念:实例,类,元类类的定义中有一个isa指针指向元类,元类结构体中又有个isa指针指向根元类 详情参考下...

  • 对isa、IMP、SEL理解

    ISA 每一个类都会有isa指针,该指针指向类的结构体,如在底层objc_msgSent() 就是通过isa来查找...

  • isa指针的作用

    原文:iOS面试题大全 对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。...

网友评论

      本文标题:ISA指向、类结构

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