OC底层原理(四):类的内存结构分析

isa分析到元类(metalClass)

用来获取类信息的掩码,将isa的地址&ISA_MASK,可以解析得到类的信息,ISA_MASK是系统定义的,不同架构不同,我在objc4源码内拿到的
x86_64与模拟器: define ISA_MASK 0x00007ffffffffff8ULL
arm64: define ISA_MASK 0x0000000ffffffff8ULL

新建一个LGPerson对象的实例p,通过lldb动态调试探索isa指向关系:
对象(object) –isaisa > 类(class) –isaisa > 元类(metaClass)

代码:

@interface LGPerson : NSObject

@end

@implementation LGPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // nonpointerIsa掩码0x00007ffffffffff8
        LGPerson *p = [LGPerson alloc];
        NSLog(@"%@",p);
        // 0x0000000100008360 VS 0x0000000100008338
        // LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
        // 0x0000000100008338 不是类 它是什么
        // 新的东西
        
        // isa 的走位
        // 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
        // 根类 isa -> 根元类 isa
        // 继承链
    }
    return 0;
}
复制代码

断点调试图:

Screenshot 2021-06-23 at 4.47.43 PM.png

调试打印:

(lldb) x p
0x1005b3870: 65 83 00 00 01 80 1d 01 00 00 00 00 00 00 00 00  e...............
0x1005b3880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
(lldb) p/x p
(LGPerson *) $1 = 0x00000001005b3870
(lldb) x/4gx 0x00000001005b3870
0x1005b3870: 0x011d800100008365 0x0000000000000000
0x1005b3880: 0x0000000000000000 0x0000000000000000
(lldb) 0x011d800100008365 是 我们对象的 isa
error: '0x011d800100008365' is not a valid command.
(lldb) p 0x011d800100008365 & 0x00007ffffffffff8
(long) $2 = 4295000928
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson

(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff808be008
0x100008370: 0x00007fff204afaa0 0x0000802c00000000
(lldb) po 0x0000000100008338 & 0x00007ffffffffff8
LGPerson

(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $6 = 0x0000000100008338
(lldb) $6 != $3
复制代码

结论:

  1. p/x p 打印LGPerson的实例对象p的内存地址
  2. x/4gx 0x00000001005b3870 格式化打印0x00000001005b3870地址下的连续地址空间内存储的数据,拿到了LGPerson对象首地址,也就是isa指针地址
  3. p/x 0x011d800100008365 & 0x00007ffffffffff8 通过isa指针和ISA_MASK的与操作,解析了LGPerson类对象首地址
  4. po 0x0000000100008360 打印这个地址数据,得到了LGPerson
  5. x/4gx 0x0000000100008360通过实例对象的isa指向类对象,我拿到了类对象内存地址0x0000000100008360,格式化输出类对象的内存地址
  6. p/x 0x0000000100008338 & 0x00007ffffffffff8将类对象的首地址(isa指针地址)0x0000000100008338ISA_MASK做与操作,得到了元类·的内存地址0x0000000100008338
  7. 0x0000000100008360 VS 0x0000000100008338虽然地址不同,但是打印都是LGPerson,是类地址元类地址的区别
  8. 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);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // nonpointerIsa掩码0x00007ffffffffff8
        LGPerson *p = [LGPerson alloc];
        NSLog(@"%@",p);
        // 0x0000000100008360 VS 0x0000000100008338
        // LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
        // 0x0000000100008338 不是类 它是元类
        // 新的东西
  
        // isa 的走位
        // 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
        // 根类 isa -> 根元类 isa
   
        // 继承链
    }
    return 0;
}
复制代码

lldb调试:

2021-06-23 16:52:21.495202+0800 002-isa分析[12154:882912] <LGPerson: 0x1005b3870>
2021-06-23 16:52:21.495968+0800 002-isa分析[12154:882912] 
0x100008360-
0x100008360-
0x100008360-
0x100008360
复制代码

Screenshot 2021-06-23 at 4.56.25 PM.png
结论:

LGPerson类对象在内存只有一份,跟对象不一样!

烂苹果分析类与元类内存

进一步使用MatchOview烂苹果分析002-isa分析.exec

Screenshot 2021-06-23 at 5.10.03 PM.png

结论:

LGPerson的类对象元类exec中只有一份

元类(metaClass)的isa->根元类(rootMetaClass)

由上面一节可知道类的isa是元类,那元类的isa就是根元类

调试打印:

(lldb) x/4gx p
0x10064e270: 0x011d800100008365 0x0000000000000000
0x10064e280: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008360
(lldb) x/4gx 0x0000000100008360
0x100008360: 0x0000000100008338 0x00007fff808be008
0x100008370: 0x00007fff204afaa0 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8
(long) $2 = 0x0000000100008338
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff808bdfe0 0x00007fff808bdfe0
0x100008348: 0x00007fff204afaa0 0x0000e03500000000
(lldb) p/x 0x00007fff808bdfe0 & 0x00007ffffffffff8
(long) $3 = 0x00007fff808bdfe0
(lldb) po 0x00007fff808bdfe0
NSObject
(lldb) 
复制代码

调试图:
Screenshot 2021-06-23 at 5.23.45 PM.png

结论:

  1. x/4gx 0x0000000100008338 通过类对象isa指针找到了元类对象地址,格式化输出元类对象地址,得到了元类对象的首地址,即isa指针0x0000000100008338
  2. p/x 0x0000000100008338 & 0x00007ffffffffff8 同样,将isa指针地址和ISA_MASK做与操作,得到了一个地址0x00007fff808bdfe0
  3. po 0x00007fff808bdfe0 输出地址0x00007fff808bdfe0,打印了NSObject对象。
  4. 根元类(rootMetaClass)就是NSObject

NSObject的根元类是

(lldb) p/x NSObject.class
(Class) $5 = 0x00007fff808be008 NSObject
(lldb) x/4gx 0x00007fff808be008
0x7fff808be008: 0x00007fff808bdfe0 0x0000000000000000
0x7fff808be018: 0x0000000100648050 0x0001801000000003
(lldb) p/x 0x00007fff808bdfe0 & 0x00007ffffffffff8
(long) $6 = 0x00007fff808bdfe0
(lldb) po 0x00007fff808bdfe0
NSObject
复制代码

Screenshot 2021-06-23 at 5.31.55 PM.png

继承链

isa流程图.png

验证自定义类与子类的superClass与isa

新建LGTeacher类继承于LGPerson类,通过方法验证类的继承关系跟上图一致:

  1. subClass类根元类superClass类根元类一致是NSObject类
  2. subClass类父类superClass类,而superClass类父类NSObject类(superClass类继承与NSObject类)
  3. NSObject类的父类是nil,但是NSObject类元类NSObject根元类;而NSObject根元类父类NSObject类,它的根根元类就是自身!

代码:

@interface LGPerson : NSObject

@end

@implementation LGPerson

@end

@interface LGTeacher : LGPerson

@end

@implementation LGTeacher

@end

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(@"LGPerson的元类的父类: %@ - %p",psuperClass,psuperClass);
    
    // LGTeacher -> LGPerson -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"LGTeacher的元类的父类: %@ - %p",tsuperClass,tsuperClass);
    
    // NSObject 根类特殊情况
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject的元类的父类: %@ - %p",nsuperClass,nsuperClass);
    // 根元类 -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"NSObject根元类的父类: %@ - %p",rnsuperClass,rnsuperClass);

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 0x00007ffffffffff8
        LGPerson *p = [LGPerson alloc];
        NSLog(@"%@",p);
        // 0x0000000100008360 VS 0x0000000100008338
        // LGPerson : 猜想 类会和我们的对象 无限开辟 内存不止有一个类
        // 0x0000000100008338 不是类 它是元类
        // 新的东西
        
        // isa 的走位
        // 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
        // 根类 isa -> 根元类 isa
        
        // 继承链
        lgTestNSObject();
    }
    return 0;
}
复制代码
2021-06-25 23:07:44.993380+0800 002-isa分析[33538:2348495] 
0x1007042a0 实例对象
0x7fff808be008 类
0x7fff808bdfe0 元类
0x7fff808bdfe0 根元类
0x7fff808bdfe0 根根元类
2021-06-25 23:07:44.994396+0800 002-isa分析[33538:2348495] LGPerson的元类的父类: NSObject - 0x7fff808bdfe0
2021-06-25 23:07:44.994518+0800 002-isa分析[33538:2348495] LGTeacher的元类的父类: LGPerson - 0x100008338
2021-06-25 23:07:44.994579+0800 002-isa分析[33538:2348495] NSObject的元类的父类: (null) - 0x0
2021-06-25 23:07:44.994680+0800 002-isa分析[33538:2348495] NSObject根元类的父类: NSObject - 0x7fff808be008
(lldb) 
复制代码

结论:

  1. NSOject对象的元类与根元类是同一个类
  2. 元类间也存在着继承的关系,跟类是一样的,通过打印LGTeacher元类superClassLGPerson元类都得到了一样的地址0x100008338
  3. 根元类的父类指向了NSObject,通过打印NSObjectsuperClass与NSObject类得到了0x7fff808be008地址
  4. NSObject的父类是(null),地址为0x0,即NSObject没有父类

类的结构分析

新旧版本的objc_class源码

老版本1.0的objc_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;
/* Use `Class` instead of `struct objc_class *` */
复制代码

新版本2.0的objc_class(基于objc4(818版本)的源码)

可以省略,因为里面大部分方法现在不做讲解,只用注意它的3大变量
Class superclass保存父类内存地址,占8字节内存
cache_t cache 结构体占16字节内存,具体内容下一章说明
class_data_bits_t bits 管理有类属性property、方法 method等等的结构体,内存大小由定义property、method等等多少定

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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的内存结构

代码:

#import <Foundation/Foundation.h>

@interface LGPerson

@end

@implementation LGPerson

@end

#ifdef DEBUG
#define LGNSLog(format, ...) printf("KC打印: %s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGNSLog(format, ...);
#endif

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 普通指针

        int a = 10; //
        int b = 10; //
        LGNSLog(@"%d -- %p",a,&a);
        LGNSLog(@"%d -- %p",b,&b);
        // 对象 -
        LGPerson *p1 = [LGPerson alloc];
        LGPerson *p2 = [LGPerson alloc];
        LGNSLog(@"%@ -- %p",p1,&p1);
        LGNSLog(@"%@ -- %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);

        for (int i = 0; i<4; i++) {
            int value =  *(d+i);
            NSLog(@"%d",value);
        }
        // OC 类 结构 首地址 - 平移一些大小 -> 内容
        // LGPerson.class地址 - 平移 所有的值 
        NSLog(@"指针 - 内存偏移");
    }
    return 0;
}
复制代码

调试打印:

KC打印: 10 -- 0x7ffeefbff36c
KC打印: 10 -- 0x7ffeefbff368
KC打印: <LGPerson: 0x105c040c0> -- 0x7ffeefbff360
KC打印: <LGPerson: 0x105c040d0> -- 0x7ffeefbff358
2021-06-24 16:44:03.478075+0800 002-内存偏移[23027:1912018] 0x7ffeefbff380 - 0x7ffeefbff380 - 0x7ffeefbff384
2021-06-24 16:44:03.478816+0800 002-内存偏移[23027:1912018] 0x7ffeefbff380 - 0x7ffeefbff384 - 0x7ffeefbff388
2021-06-24 16:50:34.623442+0800 002-内存偏移[23027:1912018] 1
2021-06-24 16:50:34.623577+0800 002-内存偏移[23027:1912018] 2
2021-06-24 16:50:34.623656+0800 002-内存偏移[23027:1912018] 3
2021-06-24 16:50:34.623712+0800 002-内存偏移[23027:1912018] 4
2021-06-24 16:50:34.624118+0800 002-内存偏移[23027:1912018] 指针 - 内存偏移
Program ended with exit code: 0
复制代码

结论:

 变量:值拷贝
 对象:指针地址与内容地址都不同
 数组:首地址作为数组地址
 
复制代码

类的内存结构

通过打印class的内存内容,要查找class_data_bits_t的结构体去了解接下来的属性与方法、isa

错误的理念

通过使用ojc4.8的工程编译断点调试!错误的初步推断class的内存分布

Screenshot 2021-06-25 at 12.07.06 AM.png

错误理解图:
Screenshot 2021-06-25 at 12.14.52 AM.png

结论:
之所以错误了,就是没有具体分析了cache_t的结构体内容

正确的理念

要具体分析cache_t的结构体占用的内存大小
复制代码

cache_t结构体里方法不会在内存里,static变量是在全局区也不在其占用内存大小里

struct cache_t {
//只有这个部分占用结构体的内存
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //typedef unsigned long         uintptr_t;
        //由于这个是泛型,所以大小由unintptr_t确定,而unsigned long占用8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;//typedef uint32_t mask_t; 
            //由于unit32_t占用4字节
#if __LP64__
            uint16_t                   _flags; //2字节
#endif
            uint16_t                   _occupied; //2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;//结构体指针只占用8字节
    };//这个共用体刚好占用8字节
    
  .....
  //省略了方法与staic变量,因为他们不占用cache_t开拓的内存
  .....
  
 }// 这个结构体刚好占用8+8=16字节
复制代码

真正查找到class_data_bits_t

由于上面验证可以得出cache_t是占用16字节,而Class ISA占用8字节,Class superclass占用8字节;所以查找class_data_bits_t正好是class指针地址+0x20

断点调试:

KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
(lldb) po 0x000000010036a140
NSObject

(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
(lldb) 0x0002802800000003 -> bit -> data
error: '0x0002802800000003' is not a valid command.
(lldb) x/5gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
0x1000083a0: 0x0000000100664ed4
(lldb) x/6gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036a140
0x100008390: 0x0000000100664f10 0x0002802800000003
0x1000083a0: 0x0000000100664ed4 0x000000010036a0f0
(lldb) po sizeof(LGPerson.class)
8

(lldb) 要获取class_data_bits_t的地址内容要首地址(8bytes)+ superclass(8bytes) + cache(16bytes) = 32
error: '要获取class_data_bits_t的地址内容要首地址(8bytes)+' is not a valid command.
(lldb) po 0x100008380+0x20
4295000992

(lldb) p/x 0x100008380+0x20
(long) $7 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)0x00000001000083a0
(class_data_bits_t *) $8 = 0x00000001000083a0
(lldb) p *$8
(class_data_bits_t) $9 = (bits = 4301672148)
(lldb) p $8->data()
(class_rw_t *) $10 = 0x0000000100664ed0
(lldb) p *$10
(class_rw_t) $11 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

复制代码

结论:

  1. 0x000000010036a140LGPersonsuperClass
  2. 0x100008380+0x20是LGPerson类的地址+16字节偏移量,刚好是class_data_bits_t

分析class_data_bits_t结构体

class_rw_t结构体

通过分析class_data_bits_t的内容可以发现它的主要数据内容就是输出class_rw_t的结构体指针

Screenshot 2021-06-25 at 1.12.18 AM.png

源代码:

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }

    //此处省略了部分代码

public:
    //重点这里输出数据
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    //此处省略了部分代码

复制代码

结论:

通过获取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

//省略了部分代码
    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;


    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    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()};
        }
    }

    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};
        }
    }

    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 *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};


复制代码

结论:

通过解析class_rw_t这个结构体可以拿到类的信息,比如这个类的firstSubclassmethodspropertiesprotocolsdeepCopyro等等信息

获取class_rw_tfirstSubclass

成员变量获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> firstSubclass

在工程中新建LGPersonLGTeacher的对象,在LGTeacher打上断点,第一个断点只实例LGPerson,第二次运行实例化LGTeacher,用于验证firstSubclass的变化。

Screenshot 2021-06-25 at 1.04.35 AM.png
断点调试

(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010070d790
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

//进入第二个断点

2021-06-25 01:02:38.456836+0800 KCObjcBuild[25790:2082491] 我来了: <LGTeacher: 0x100604d60>
(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000100726800 0x0002802800000003
(lldb) p *$2
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = LGTeacher
  nextSiblingClass = NSUUID
}
(lldb) 
复制代码

结论:

在子类LGTeacher没有实例前,LGPersonclass_rw_t中的firstSubclass是nil,侧面证明Object-C是运行时的。其实在lldb中p LGTeacher.class也可以改变,原因是OC的class是懒加载的存在!

获取class_rw_tproperties的数组

成员变量获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> property_array_t -> property_list_t -> property_t

代码:

#import <Foundation/Foundation.h>

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

- (void)sayNB;
+ (void)say666;

// 1: 类的探究分析
// 2: 类方法 + ivar 在哪里

@end

@implementation LGPerson

- (instancetype)init{
    if (self = [super init]) {
        self.name = @"Cooci";
    }
    return self;
}

- (void)sayNB{
    
}
+ (void)say666{
    
}

@end

@interface LGTeacher : LGPerson
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end

@implementation LGTeacher
- (instancetype)init{
    if (self == [super init]) {
        NSLog(@"我来了: %@",self);
        return self;
    }
    return nil;
}

- (void)teacherSay{
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 类的内存结构
        // 并非 8 * 3 = 24 (错误的内存大小)
        //
       
        LGPerson *p1 = [[LGPerson alloc] init];
        LGTeacher *p2 = [[LGTeacher alloc] init];
        
        // class_data_bits_t
    }
    return 0;
}
复制代码

断点调试:

(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000101907be0 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)0x00000001000083a8
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001019080a0
(lldb) p $3->properties()
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008260
      }
      arrayAndFlag = 4295000672
    }
  }
}
(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(0)
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $7.get(1)
(property_t) $9 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
复制代码

步骤:

  1. x/4gx LGPerson.class 格式化输出LGPerson.class,拿到类的首地址0x100008388
  2. p/x 0x100008388 + 0x20 首地址偏移32个字节(isa8字节、superclass8字节、cache16字节),拿到类对象属性地址0x00000001000083a8
  3. p (class_data_bits_t *)0x00000001000083a8 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_tdata()函数,拿到class_rw_t类型的地址0x00000001019080a0
  5. p $3->properties() 通过properties()函数获取LGPerson的成员变量列表
  6. p $4.listp $5.ptr解析出property_list_t的地址
  7. p *$6 通过取地址的方式获取成员变量property_list_t
  8. p $7.get(0)p $7.get(1) 通过c++函数单个获取类的成员变量namehobby

获取class_rw_tmethods的数组

实例方法获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> big

与properties()一样,但是是获取class_rw_t对象的method()方法,有区别的地方在于当拿到method之后并不能直接向properties一样就可以解析出来,在method_t结构体中还有一层结构体big(),所以在获取实例方法的时候得多解析一层结构体。但是过程中发现没有类方法!说明这个是实例方法

源代码:

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
//此处省略了部分源码
}

复制代码

对比method与propetry异同图:
Screenshot 2021-06-25 at 1.46.57 AM.png

断点调试:

(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x0000000101304120 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)0x00000001000083a8
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001013040a0
(lldb) p $3->methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
(lldb) p $4.list
(const method_list_t_authed_ptr<method_list_t>) $5 = {
  ptr = 0x0000000100008160
}
(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100008160
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "sayNB"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "hobby"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "setHobby:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "init"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "name"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson name])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "setName:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setName:])
}
复制代码

步骤:

  1. x/4gx LGPerson.class 格式化输出LGPerson.class,拿到类的首地址0x100008388
  2. p/x 0x100008388 + 0x20 首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址0x00000001000083a8
  3. p (class_data_bits_t *)0x00000001000083a8 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_tdata()函数,拿到class_rw_t类型的地址0x00000001013040a0
  5. p $3->methods() 通过methods()函数获取LGPerson的实例方法列表method_array_t
  6. p $4.listp $5.ptr解析出method_list_t的地址
  7. p *$6 通过取地址的方式获实例变量数组method_list_t entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6),非常明显有6个实例方法
  8. 通过c++函数get()big()单个获取类的实例方法:
    p $7.get(0).big():-[LGPerson sayNB]自定义实例方法sayNB
    p $7.get(1).big():-[LGPerson hobby]成员变量hobbygetter方法,是系统生成的
    p $7.get(2).big():-[LGPerson init]自定义init方法- (instancetype)init
    p $7.get(3).big():-[LGPerson name]成员变量name的getter方法,是系统生成的
    p $7.get(0).big():-[LGPerson setName:]成员变量name的setter方法,是系统生成的

获取class_rw_t的ivars(成员变量)

ivars获取流程源码:NSObject.class -> class_data_bits_t -> class_rw_t -> class_ro_t -> ivar_list_t -> ivar_t

源代码:

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

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
//省略了部分代码
}
复制代码
struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

复制代码

lldb动态调试

(lldb) x/4gx LGPerson.class
0x100008388: 0x00000001000083b0 0x000000010036a140
0x100008398: 0x000000010135c630 0x0002802800000003
(lldb) p/x 0x100008388 + 0x20
(long) $1 = 0x00000001000083a8
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x00000001000083a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010135c5f0
(lldb) p $3->ro()
(const class_ro_t *) $4 = 0x0000000100008118
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 0
  instanceStart = 8
  instanceSize = 32
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003ea8 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008160
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000081f8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008260
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000081f8
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008320
  name = 0x0000000100003f2f "subject"
  type = 0x0000000100003f7f "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x0000000100008328
  name = 0x0000000100003f37 "_name"
  type = 0x0000000100003f7f "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
  offset = 0x0000000100008330
  name = 0x0000000100003f3d "_hobby"
  type = 0x0000000100003f7f "@\"NSString\""
  alignment_raw = 3
  size = 8
}
复制代码

步骤

  1. x/4gx LGPerson.class 格式化输出LGPerson.class,获取到首地址0x100008388
  2. p/x 0x100008388 + 0x20 首地址偏移32字节(isa8字节、superclass8字节、cache_t16字节),拿到包含类属性方法成员变量的对象class_data_bits_t的地址0x00000001000083a8
  3. p (class_data_bits_t *)$1 将地址转换为class_data_bits_t,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_tdata()函数,拿到class_rw_t类型的地址0x000000010135c5f0
  5. p $3->ro 使用class_rw_tro函数,拿到class_ro_t类型的地址0x0000000100008118
  6. p *$4lass_ro_t类型地址0x0000000100008118的值,拿到了class_ro_t对象
  7. p $5.ivars 使用ivars函数获取class_ro_t对象的ivars,得到了指向ivar_list_t地址0x00000001000081f8的指针
  8. p *$6 通过取地址0x00000001000081f8的方式获实例变量数组ivar_list_t,entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3),可以看到有3个ivars
  9. 通过c++函数get()单个获取类的实例方法:

用户声明的成员变量subject
p $7.get(0):
(ivar_t) $8 = {
offset = 0x0000000100008320
name = 0x0000000100003f2f “subject
type = 0x0000000100003f7f “@”NSString“”
alignment_raw = 3
size = 8
}

属性name,系统自行生成变量_name
p $7.get(1):
(ivar_t) $9 = {
offset = 0x0000000100008328
name = 0x0000000100003f37 “_name
type = 0x0000000100003f7f “@”NSString“”
alignment_raw = 3
size = 8
}

属性hobby,系统自行生成变量_hobby
p $7.get(2):
(ivar_t) $10 = {
offset = 0x0000000100008330
name = 0x0000000100003f3d “_hobby
type = 0x0000000100003f7f “@”NSString“”
alignment_raw = 3
size = 8
}

结论:ivars存在ro中,成员变量自动生成了属性_name,_hobby

获取class_rw_t的类方法

上面我们已经获取过class的方法列表,发现没有类方法,所以类Class直接获取的methods实例方法,而类方法无处去吗?那类方法只能是元类metalClass里面了。

类方法获取流程:NSObject.class -> metaClass -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> big

断点调试打印:

(lldb) x/4gx LGPerson.class
0x100008390: 0x00000001000083b8 0x000000010036a140
0x1000083a0: 0x0000000101470ab0 0x0002802800000003
(lldb) p/x 0x00000001000083b8 & 0x00007ffffffffff8
(long) $1 = 0x00000001000083b8
(lldb) x/4gx 0x00000001000083b8
0x1000083b8: 0x000000010036a0f0 0x000000010036a0f0
0x1000083c8: 0x0000000100731720 0x0001e03100000007
(lldb) p/x 0x1000083b8 + 0x20
(long) $2 = 0x00000001000083d8
(lldb) p (class_data_bits_t *)$2
(class_data_bits_t *) $3 = 0x00000001000083d8
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000101470a50
(lldb) p $4->methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000082d0
      }
      arrayAndFlag = 4295000784
    }
  }
}
(lldb) p $5.list
(const method_list_t_authed_ptr<method_list_t>) $6 = {
  ptr = 0x00000001000082d0
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x00000001000082d0
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
  name = "say666"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003e10 (KCObjcBuild`+[LGPerson say666])
}
(lldb) 
复制代码

步骤:

  1. x/4gx LGPerson.class格式化打印类LGPerson,得到类的首地址0x100008390
  2. p/x 0x00000001000083b8 & 0x00007ffffffffff8isa指针ISA_MASK操作,拿到LGPerson的元类metaClass
  3. x/4gx 0x00000001000083b8,格式化打印LGPersonmetaClass,拿到元类的首地址0x1000083b8
  4. p/x 0x1000083b8 + 0x20,将元类的首地址偏移32个字节(ISA8字节、superclass8字节、cache_t16字节),那多元类的class_data_bits_t对象地址0x00000001000083d8
  5. p (class_data_bits_t *)0x00000001000083d8将地址转化为class_data_bits_t对象,方便调用函数
  6. p $3->data()调用class_data_bits_t的data函数,拿到class_rw_t对象地址0x0000000101470a50
  7. p $4->methods()获取class_rw_tmethods方法列表
  8. p $5.listp $5.ptr拿到指向method_list_t地址的指针0x00000001000082d0
  9. p *$7取地址,拿到了method_list_t对象,count为1,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1), 有一个类方法
  10. 通过c++函数get()big()单个获取类的类方法:
  11. p $7.get(0).big():

(method_t::big) $9 = {
name = “say666
types = 0x0000000100003f77 “v16@0:8”
imp = 0x0000000100003e10 (KCObjcBuild+[LGPerson say666])
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享