ios-OC-对象-类-元类关系探究

首先我们知道对象创建开辟内存,存放isa指针以及其他成员变量的值。

类的探究

  • LGPerson *p = [LGPerson alloc]; 创建一个对象

  • 获取对象 p 地址

    • p/x
    • 得到 (LGPerson *) $2 = 0x000000010058c5f0

    image.png

  • 根据地址获取数据

    • x/4gx 0x000000010058c5f0
    • 结果:

    0x10058c5f0: 0x011d800100008365 0x0000000000000000
    0x10058c600: 0x0000000000000000 0x0000000000000000

  • 获取isa 0x011d800100008365

  • isa & 掩码 获取对象的类地址

    • p/x 0x011d800100008365 & 0x7ffffffffff8
    • 得到结果:(long) $3 = 0x0000000100008360
    • po 0x0000000100008360 得到 LGPerson
  • 获取类地址获取数据

    • x/4gx 0x0000000100008360 (类地址)
    • 结果:

    0x100008360: 0x0000000100008338 0x00007fff808b2008
    0x100008370: 0x00007fff204a3aa0 0x0000802c00000000

  • 获取到类的isa 0x0000000100008338

  • 类isa & 掩码 获取元类地址 并打印

    • po 0x0000000100008338 & 0x7ffffffffff8
    • 结果 LGPerson
    • p/x 0x0000000100008338 & 0x7ffffffffff8
    • (long) $6 = 0x0000000100008338 发现还是 8338
  • 思考 0x0000000100008360 地址 和 0x0000000100008338 地址 都是打印的LGPerson

  • 猜想:类和对象一样也会开辟开辟内存,内存中不只有一个类

验证
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(void){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

- 结果
0x100008360-
0x100008360-
0x100008360-
0x100008360

- 说明真正的类地址是 100008360 不是 100008338 类地址只有一个,
- 100008338则是元类地址
复制代码

可执行文件分析

  • 利用MachOView 打开可执行文件

image.png

  • 打开 __objc_classrefs 发现并没有mataclass的信息 只有对象的信息

image.png

  • Symbol Tabel 中查找 MetaClass – 发现了一个 _objc_metaclass_$_LGPerson $ 链接符号 无实义

image.png

isa 指向分析

  • 元类由系统编译完成创建由类的isa&掩码指向

    • 对象instance.isa & 掩码 -> 类.isa & 掩码 -> 元类.isa & 掩码 -> 根元类
  • 打印元类地址 (lldb) x/4gx 0x0000000100008338

    • 结果
      0x100008338: 0x00007fff808b1fe0 0x00007fff808b1fe0
      0x100008348: 0x000000010063f080 0x0001e03500000007
  • 元类ISA & 掩码

    • (lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
    • 结果:(long) $7 = 0x00007fff808b1fe0
  • po 打印

    • (lldb) po 0x00007fff808b1fe0
    • 结果:NSObject
  • 元类的isa

  • NSObject 元类探究

    • 1.获取NSObject 类地址

      • (lldb) p/x NSObject.class
      • 结果:(Class) $9 = 0x00007fff808b2008 NSObject
    • 2.获取数据

      • (lldb) x/4gx 0x00007fff808b2008
      • 结果:

      0x7fff808b2008: 0x00007fff808b1fe0 0x0000000000000000
      0x7fff808b2018: 0x0000000100586990 0x0002801000000003

    • 3.NSObject类.ISA & 掩码 获取 NSObject元类

      • (lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
      • 结果:(long) $10 = 0x00007fff808b1fe0
  • 根类.isa & 掩码 -> 根元类

  • 结论图:这里我们先放下继承的关系先仅仅从isa的指向得出结论

    • 对象instance.isa & 掩码 -> 类.isa & 掩码 -> 元类.isa & 掩码 -> 根元类

    image.png

  • 代码验证

###### 代码打印
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);

- 结果
0x100705b50 实例对象
0x7fff808b2008 类
0x7fff808b1fe0 元类
0x7fff808b1fe0 根元类
0x7fff808b1fe0 根根元类

复制代码
  • 刚好验证了这幅图的结果

image.png

继承 superclass 对isa指向的影响分析

  • 还是代码查看
#pragma mark - NSObject 元类链
void lgTestNSObject(void){
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    // LGPerson元类
    Class pMetaClass = object_getClass(LGPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"%@ - %p",psuperClass,psuperClass);
    
    // LGTeacher -> LGPerson -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"%@ - %p",tsuperClass,tsuperClass);
}

复制代码
  • 结果 这里我补充了几个地址
0x100504990 实例对象
0x7fff808b2008 类
0x7fff808b1fe0 元类
0x7fff808b1fe0 根元类
0x7fff808b1fe0 根根元类
2021-06-19 23:06:45.555472+0800 002-isa分析[1518:109172] NSObject - 0x7fff808b1fe0
2021-06-19 23:06:45.555585+0800 002-isa分析[1518:109172] LGPerson - 0x100008338
(lldb) p LGPerson.class
(Class) $0 = LGPerson
(lldb) p LGPerson.class
(Class) $1 = LGPerson
(lldb) p/x LGPerson.class
(Class) $2 = 0x0000000100008360 LGPerson
2021-06-19 23:12:10.220883+0800 002-isa分析[1518:109172] (null) - 0x0
(lldb) p pMetaClass
(Class) $3 = 0x0000000100008338
(lldb) p LGTeacher.class
(Class) $4 = LGTeacher
(lldb) p/x LGTeacher.class
(Class) $5 = 0x0000000100008310 LGTeacher
(lldb) p tMetaClass
(Class) $6 = 0x00000001000082e8
(lldb) p/x 0x00000001000082e8
(long) $7 = 0x00000001000082e8
(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff808b1fe0 0x0000000100008338
0x1000082f8: 0x00000001038223c0 0x0002e03500000003
(lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
(long) $8 = 0x00007fff808b1fe0
(lldb) 
复制代码
  • 直观看图

image.png

  • 注意:class_getSuperclass 这个函数不要等同于 通过isa & 掩码 这个只是查找父类 所以

Class tsuperClass = class_getSuperclass(tMetaClass); 找到的是8338

(lldb) p tMetaClass
(Class) $6 = 0x00000001000082e8
(lldb) p/x 0x00000001000082e8
(long) $7 = 0x00000001000082e8
(lldb) x/4gx 0x00000001000082e8
0x1000082e8: 0x00007fff808b1fe0 0x0000000100008338
0x1000082f8: 0x00000001038223c0 0x0002e03500000003
(lldb) p/x 0x00007fff808b1fe0 & 0x7ffffffffff8
(long) $8 = 0x00007fff808b1fe0
以上才是正确查找 通过isa 找到 刚好验证了 (subclass(meta))->(root class(meta))
复制代码
  • 继承的关系 元类也是有继承关系的
  • 还是通过上面的代码研究但是要补充一下
  • LLDB 继续调试
(lldb) p/x class_getSuperclass(LGPerson.class)
(Class _Nullable) $1 = 0x00007fff808b2008 NSObject
(lldb) p/x class_getSuperclass(tMetaClass)
(Class _Nullable) $2 = 0x0000000100008338
(lldb) p/x class_getSuperclass(LGTeacher.class)
(Class _Nullable) $3 = 0x0000000100008360 LGPerson
(lldb) p/x class_getSuperclass(tMetaClass)
(Class _Nullable) $4 = 0x0000000100008338
(lldb) p/x class_getSuperclass(NSObject.class)
(Class _Nullable) $5 = nil
(lldb) p/x class_getSuperclass(metaClass)
(Class _Nullable) $6 = 0x00007fff808b2008 NSObject
(lldb) 
复制代码
  • 直观看图

image.png

  • 整合起来就是这张图了,这张图包含了继承关系和isa的指向关系,class关系和superclass关系,感觉还是分开记比较好,当面试问你的时候想理解问的是isa关系还是继承关系,然后选择不一样的图进行回答,不然看这个图感觉会记晕了。

image.png

  • 这个就是这两条线

image.png

  • 所以得出结论对象是没有继承关系的只有类、元类才有继承关系,我们不能说 Person * p , Teacher * t,t 继承 p 。

objc_class 数据结构的探索

  • 查看 objc-runtime-new

image.png
#if __has_feature(ptrauth_calls)

  • __has_feature : 此函数功能是判断编译器是否支持某个功能
  • ptrauth_calls :指针身份验证,针对arm64e架构:使用apple A12 或者更高版本A系列处理器设备(如:XS、XS max 、XR 或更新设备)支持arm64e
  • objc_class 继承自 objc_object 结构体 objc_object结构体中有 isa,所以objc_class有个隐藏属性isa
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码
  • 所以这个图就比较好理解了,isa是一个联合体,利用位域存放了这个类的各种信息。

image.png

  • superclass 当前类的父类

  • catche : 缓存(目前不讨论)

  • 现在我们研究的时 class_data_bits_t bits

  • 内存空间是一段连续的内存空间,即我们想要获取对象的第一个属性的值得时候只需要将地址+8字节就可以获取第一个属性的值或者地址。同理我们想要获取bits 数组只需要将objc_class的地址+isa+superclass+cache就可以拿到bits的地址。

  • 由于cache_t 数据结构超级长,不好查看知道他的长度大小,但是思考,方法不在内存中,static的数据也不是放在内存中,在全局区。

image.png

  • 只需要查看这部分的内存就可以了。
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
复制代码
  • explicit_atomic<uintptr_t> _bucketsAndMaybeMask; 泛型数据
    • uintptr_t typedef unsigned long uintptr_t; 无符号Long 8 字节大小
  • 联合体只需要查看最大长度即可,根据取其子数据最大大小,
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; 4字节
#if __LP64__
            uint16_t                   _flags;    2字节
#endif
            uint16_t                   _occupied; 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; 指针8字节
    };

复制代码
  • typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
    • uint32 4字节
    • uint16_t 2字节
  • cache_t 为16 字节
  • LLDB 调试 : (lldb) po sizeof(cache_t) 16
  • isa+superclass+cache = 8+8+16 = 32 = 0x20 就可以拿到bits的地址。

查找属性流程 LLDB

(lldb) p/x LGPerson.class 
(Class) $5 = 0x0000000100008380 LGPerson
(lldb) p/x 0x0000000100008380+0x20
(long) $6 = 0x00000001000083a0
(lldb) p/x (class_data_bits_t *)0x00000001000083a0 //转换类型重新打印
(class_data_bits_t *) $7 = 0x00000001000083a0

-------- 找到 class-rw-t

(lldb) x/4gx LGTeacher.class 
0x100008330: 0x0000000100008358 0x0000000100008380
0x100008340: 0x00000001003623a0 0x0000802800000000
(lldb) p/x (class_data_bits_t *)0x100008350
(class_data_bits_t *) $14 = 0x0000000100008350
(lldb) p $14->data()
(class_rw_t *) $15 = 0x00000001013111f0
(lldb) p *$15
(class_rw_t) $16 = {
  flags = 2282225664
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4314921008
    }
  }
  firstSubclass = nil
  nextSiblingClass = nil
}
(lldb) p $14->data()->ro()->instanceSize
(const uint32_t) $18 = 32 这个就是对象未对齐时需要的内存大小

(lldb) p $16.properties()
(const property_array_t) $19 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000080b8
      }
      arrayAndFlag = 4295000248
    }
  }
}
-- 获取二维数组list
(lldb) p $19.list
(const RawPtr<property_list_t>) $20 = {
  ptr = 0x00000001000080b8
}
(lldb) p $20.ptr
(property_list_t *const) $21 = 0x00000001000080b8

(lldb) p *$21      //请不要在意 21变到了26 因为我上面调试出了点问题
(property_list_t) $26 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $26.get(0)
(property_t) $27 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) 



------- 在练习一次- 
(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x00000001012b64f0 0x0002802800000003
0x1000083a0: 0x00000001012b6494 0x000000010036a0f0
(lldb) p 0x1000083a0
(long) $1 = 4295000992
(lldb) p (class_data_bits_t *) 0x1000083a0
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()                    
(class_rw_t *) $3 = 0x00000001012b6490
(lldb) p $3.properties()
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) p $4.list
(const RawPtr<property_list_t>) $5 = {
  ptr = 0x0000000100008260
}
(lldb) p $5.ptr
(property_list_t *const) $6 = 0x0000000100008260
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $7.get(1)
(property_t) $8 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb) 
复制代码

查找方法流程

(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x000000010131a510 0x0002802800000003
0x1000083a0: 0x000000010131a4d4 0x000000010036a0f0
(lldb) p (class_data_bits_t *) 0x1000083a0
(class_data_bits_t *) $1 = 0x00000001000083a0
(lldb) p $2->methods()
error: <user expression 2>:1:1: use of undeclared identifier '$2'
$2->methods()
^
(lldb) p $1.data()
(class_rw_t *) $2 = 0x000000010131a4d0
  Fix-it applied, fixed expression was: 
    $1->data()
(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
(lldb) p$3.list
error: unknown command shorthand suffix: '$3.list'
(lldb) p $3.list
(const method_list_t_authed_ptr<method_list_t>) $4 = {
  ptr = 0x0000000100008160
}
(lldb) p $4.ptr
(method_list_t *const) $5 = 0x0000000100008160
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $6.get(0).big()
(method_t::big) $7 = {
  name = "hobby"
  types = 0x0000000100003f53 "@16@0:8"
  imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) 
复制代码

成员变量的查找

(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008368 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100008368
(class_data_bits_t *) $1 = 0x0000000100008368
(lldb) p (class_data_bits_t *)0x0000000100008388
(class_data_bits_t *) $2 = 0x0000000100008388
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101107400
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000320
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x0000000100008100
(lldb) p *$5
(const class_ro_t) $6 = {
  flags = 0
  instanceStart = 8
  instanceSize = 32
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003ec8 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008148
  baseProtocols = nil
  ivars = 0x00000001000081e0
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008248
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x00000001000081e0
(lldb) p *$7
(const ivar_list_t) $8 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $8.get(0)
(ivar_t) $9 = {
  offset = 0x0000000100008300
  name = 0x0000000100003f04 "hobby"
  type = 0x0000000100003f4c "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
  offset = 0x0000000100008308
  name = 0x0000000100003f0a "_age"
  type = 0x0000000100003f58 "i"
  alignment_raw = 2
  size = 4
}
(lldb) p $8.get(2)
(ivar_t) $11 = {
  offset = 0x0000000100008310
  name = 0x0000000100003f0f "_name"
  type = 0x0000000100003f4c "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) 
复制代码
  • 这个是创建空间时的-请查看以往的文章
//取出数据
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
复制代码
  • 结论:

    • 成员变量在 类.bits.data().properties() 的 properties_list_t 数组中
    • 方法在 类.bits.data().methods() 的 method_array_t 数组中
    • 成员变量在 类.bits.data().ro().ivars 的 ivar_list_t 数组中 。
  • 调用ro 方法

image.png

  • 我们可以调用方法获取数据

image.png

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 *>(&ro_or_rw_ext)->properties;
    } else {
        return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
    }
}
-----------------
list_array_tt : 二维数组,数组中的元素还是数组。
class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
-------
class list_array_tt {
....
//迭代器方法。遍历查找
class iterator{
    ....
}
....

}
复制代码
  • 查找属性
class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK); //属性是放在 class_rw_t 中 的 properties
}
复制代码
  • 查找方法
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 *>(&ro_or_rw_ext)->methods;
    } else {
        return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
    }
}

复制代码
  • 属性可以直接查找property_t 就可以找到 name和描述

image.png

  • 方法可以查找method_t中的big 调用big()方法 就可以找到 name和描述

image.png

image.png

- 这个是 hobby的get方法
(method_t::big) $7 = {
  name = "hobby" //方法名
  types = 0x0000000100003f53 "@16@0:8" //方法类型
  imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson hobby]) //方法的地址
}
复制代码
  • 图形说明位置

image.png

image.png

类方法的探究

  • 在MachOView中其实是可以找到的。

image.png

类方法查找流程

(lldb) x/4gx LGPerson.class
0x100008368: 0x0000000100008390 0x000000010036a140
0x100008378: 0x0000000101814150 0x0002802800000003
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x0000000100008390
(lldb) p/x (class_data_bits_t *) 0x00000001000083b0
(class_data_bits_t *) $5 = 0x00000001000083b0
(lldb) p $5->data()
(class_rw_t *) $6 = 0x00000001018133b0
(lldb) p *$6
(class_rw_t) $7 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000688
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff80355eb0
}
(lldb) p $7.methods()
(const method_array_t) $8 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000082b8
      }
      arrayAndFlag = 4295000760
    }
  }
}
(lldb) p $8.list
(const method_list_t_authed_ptr<method_list_t>) $9 = {
  ptr = 0x00000001000082b8
}
(lldb) p $9.ptr
(method_list_t *const) $10 = 0x00000001000082b8
(lldb) p *$10
(method_list_t) $11 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $11.get(0).big()
(method_t::big) $12 = {
  name = "say666"
  types = 0x0000000100003f44 "v16@0:8"
  imp = 0x0000000100003e30 (KCObjcBuild`+[LGPerson say666])
}
(lldb) 
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享