004-类的内存结构

通过这篇文章可以获得什么:

isa指向图分析

isa流程图.png

isa的掩码

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

我这里探索过程是创建了一个FFPerson对象的实例person,通过lldb动态调试类探索isa的指向关系:

案例代码:

#import <Foundation/Foundation.h>
#import "FFPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //isa_mask 0x00007ffffffffff8ULL
        FFPerson *person = [FFPerson alloc];
        NSLog(@"%@",person);
    }
    return 0;
}
复制代码

实例对象的isa指向类对象

  1. p/x person 格式化打印person实例对象的内存地址
  2. x/4gx 0x000000010058ccc0 格式化打印0x000000010058ccc0地址下的连续地址空间内存储的数据,拿到了person对象的首地址,也就是isa指针地址
  3. p/x 0x011d8001000080e9 & 0x00007ffffffffff8 通过isa指针ISA_MASK操作,解析了FFPerson类对象
  4. po 0x00000001000080e8 打印这个地址数据,得到了FFPerson
(lldb) p/x person
(FFPerson *) $5 = 0x000000010058ccc0
(lldb) x/4gx 0x000000010058ccc0
0x10058ccc0: 0x011d8001000080e9 0x0000000000000000
0x10058ccd0: 0x656c7552534e5b2d 0x7020726f74696445
(lldb) p/x 0x011d8001000080e9 & 0x00007ffffffffff8
(long) $6 = 0x00000001000080e8
(lldb) po 0x00000001000080e8
FFPerson
复制代码

类对象的isa指向元类(metaClass)

  1. x/4gx 0x00000001000080e8 通过实例对象的isa指向类对象,我拿到了类对象内存地址0x00000001000080e8,格式化输出类对象的内存地址
  2. p/x 0x00000001000080c0 & 0x00007ffffffffff8将类对象的首地址(isa指针地址)0x00000001000080c0ISA_MASK做与操作,得到了一个新的内存地址0x00000001000080c0
  3. po 0x00000001000080c0 打印这个地址数据,得到了FFPerson
(lldb) x/4gx 0x00000001000080e8
0x1000080e8: 0x00000001000080c0 0x00007fff88c10cc8
0x1000080f8: 0x000000010063dd70 0x0002801000000003
(lldb) p/x 0x00000001000080c0 & 0x00007ffffffffff8
(long) $8 = 0x00000001000080c0
(lldb) po 0x00000001000080c0
FFPerson
复制代码

结论一:0x00000001000080e80x00000001000080c0明显是两个内存地址,但是输出的却是一个对象,0x00000001000080e8是类对象即class0x00000001000080c0是元类对象即MetaClass

图解补充:

类对象的isa指向元类.png

元类对象的isa指向根元类(rootMetaClass)

  1. x/4gx 0x00000001000080c0 通过类对象的isa指针找到了元类对象地址,格式化输出元类对象地址,得到了元类对象的首地址,即isa指针0x00007fff88c10ca0
  2. p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8 同样,将isa指针地址和ISA_MASK操作,得到了一个地址0x00007fff88c10ca0
  3. po 0x00007fff88c10ca0 输出地址0x00007fff88c10ca0,打印了NSObject对象。
(lldb) x/4gx 0x00000001000080c0
0x1000080c0: 0x00007fff88c10ca0 0x00007fff88c10ca0
0x1000080d0: 0x0000000100704520 0x0002e03100000003
(lldb) p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
(long) $10 = 0x00007fff88c10ca0
(lldb) po 0x00007fff88c10ca0
NSObject
复制代码

结论二:所有类对象的元类对象的isa指针指向的根源类NSObject

图解补充:

元类的isa指向根元类.png

根元类的isa指针指向自己

  1. x/4gx 0x00007fff88c10ca0 格式化输出根元类的地址,得到的对象依然是根元类自己
  2. p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8 使用ISA_MASK的操作验证一下,得到了0x00007fff88c10ca0
  3. po 0x00007fff88c10ca0 打印得到结果依然是NSObject
(lldb) x/4gx 0x00007fff88c10ca0
0x7fff88c10ca0: 0x00007fff88c10ca0 0x00007fff88c10cc8
0x7fff88c10cb0: 0x000000010063e590 0x0003e03100000007
(lldb) p/x 0x00007fff88c10ca0 & 0x00007ffffffffff8
(long) $16 = 0x00007fff88c10ca0
(lldb) po 0x00007fff88c10ca0
NSObject
复制代码

superclass指向图分析

案例代码:

void ffSuperclass(void) {
    
    //NSObjcet实例对象
    NSObject *objectInstance = [NSObject alloc];
    //NSObject类
    Class object = object_getClass(objectInstance);
    //NSobject元类
    Class metaClass = object_getClass(object);
    //NSObjct根元类
    Class rootMetaClass = object_getClass(metaClass);
    
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n",objectInstance,object,metaClass,rootMetaClass);
    
    //FFPerson的元类
    Class personMetaClass = object_getClass(FFPerson.class);
    NSLog(@"FFPerson的元类 : %@ - %p",personMetaClass, personMetaClass);
    
    //FFPerson元类的父类
    Class personSuperMetaclass = class_getSuperclass(personMetaClass);
    NSLog(@"FFPerson元类的父类 : %@ - %p",personSuperMetaclass, personSuperMetaclass);
    
    //FFChildren的元类
    Class childrenMetaClass = object_getClass(FFChildren.class);
    NSLog(@"FFChildren的元类 : %@ - %p:",childrenMetaClass,childrenMetaClass);
    
    //FFchildren元类的父类
    Class childrenSuperMetaClass = class_getSuperclass(childrenMetaClass);
    NSLog(@"FFChildren元类的父类 : %@ - %p:",childrenSuperMetaClass,childrenSuperMetaClass);
    
    //NSObject类的父类
    Class superObject = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject的父类 : %@ - %p:",superObject,superObject);
    
    //NSObject根元类的父类
    Class rootSuperMetaObject = class_getSuperclass(rootMetaClass);
    NSLog(@"NSObject根元类的父类 : %@ - %p:",rootSuperMetaObject,rootSuperMetaObject);
}
复制代码

测试结果:

2021-06-17 23:21:51.513376+0800 002-isa分析[2803:167864] 
0x103804470 实例对象
0x7fff88c10cc8 类
0x7fff88c10ca0 元类
0x7fff88c10ca0 根元类

002-isa分析[2803:167864] FFPerson的元类 : FFPerson - 0x1000081c8
002-isa分析[2803:167864] FFPerson元类的父类 : NSObject - 0x7fff88c10ca0
002-isa分析[2803:167864] FFChildren的元类 : FFChildren - 0x100008178:
002-isa分析[2803:167864] FFChildren元类的父类 : FFPerson - 0x1000081c8:
002-isa分析[2803:167864] NSObject的父类 : (null) - 0x0:
002-isa分析[2803:167864] NSObject根元类的父类 : NSObject - 0x7fff88c10cc8:
复制代码

结论:

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

类的结构分析

object1.0:已经不被使用

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;
复制代码

object2.0(基于objc4(818版本)的源码)

省略了部分源码:源码位置为objc-runtime-new.h文件第1688行-2173行

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 getSuperclass() const {
#if __has_feature(ptrauth_calls)
#   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
        if (superclass == Nil)
            return Nil;
            
//省略了大部分源码
}
复制代码

Class ISA:8字节

CLass superclass:8字节

cache_t cache:16字节

typedef unsigned long uintptr_t; 无符号长整形,占8字节
preopt_cache_t * 指针占8字节
由于preopt_cache_t *这个指针类型在union联合体中,是互斥的,所以这个联合体占8字节
所以struct cache_t 一共是16字节,即cache占用16字节

源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第338行-550行

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;
    };
//省略了跟内存大小无关的代码
}
复制代码

结论一:探索ISA、superclass、cache是为了通过内存偏移找到bits

class_data_bits_t bits:

源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第1577行-1685行

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(),将会拿到这个类的methodspropertiesprotocolsdeepCopyro等等信息

class_rw_t

源码分析:省略了部分源码:源码位置为objc-runtime-new.h文件第1458行-1574行

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这个结构体可以拿到类的信息,比如这个类的methodspropertiesprotocolsdeepCopyro等等信息

获取类的属性

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

案例代码:

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;

@end
复制代码

操作流程:

获取类的成员变量.png

步骤:

  1. x/4gx LGPerson.class 格式化输出LGPerson.class,拿到类的首地址
  2. p/x 0x100008380 + 0x20 首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址
  3. p (class_data_bits_t *)0x00000001000083a0 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_tdata()函数,拿到class_rw_t类型的地址
  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++函数单个获取类的成员变量nameage

获取类的实例方法

实例方法获取流程: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(),所以在获取实例方法的时候得多解析一层结构体
源码如下:省略了部分本文章未探索的源码,源码位置为objc-runtime-new.h文件第726行-861行

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;
    };
//此处省略了部分源码
}
复制代码

案例源码:

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;

- (void)saySomething;

@end
复制代码

lldb调试流程

获取类的实例方法.png

步骤:

  1. x/4gx LGPerson.class 格式化输出LGPerson.class,拿到类的首地址
  2. p/x 0x1000083b8 + 0x20 首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址
  3. p (class_data_bits_t *)0x00000001000083d8 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_tdata()函数,拿到class_rw_t类型的地址
  5. p $3->properties() 通过methods()函数获取LGPerson的实例方法
  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 = 5),非常明显有5个实例方法
  8. 通过c++函数get()big()单个获取类的实例方法:

p $7.get(0).big():-[LGPerson saySomething]自定义实例方法saySomething
p $7.get(1).big():-[LGPerson name]成员变量name的getter方法,是系统生成的
p $7.get(2).big():-[LGPerson setName:]成员变量name的setter方法,是系统生成的
p $7.get(3).big():-[LGPerson age]成员变量age的getter方法,是系统生成的
p $7.get(0).big():-[LGPerson setAge:]成员变量age的setter方法,是系统生成的

ivars(成员变量)在哪里

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

class_ro_t源码:省略了部分本文章未探索的源码,源码位置为objc-runtime-new.h文件第1037行-1171行

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;
//省略了部分代码
}
复制代码

ivar_t源码:

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;
    }
};
复制代码

案例代码:

@interface LGPerson : NSObject
{
    NSString * hobby;
    float weight;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;

@end

复制代码

lldb动态调试

获取类的ivars.png

步骤

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

p $7.get(0):

(ivar_t) $8 = {
offset = 0x0000000100008370
name = 0x0000000100003eec “hobby”
type = 0x0000000100003f60 “@”NSString””
alignment_raw = 3
size = 8
}

p $7.get(1):

(ivar_t) $9 = {
offset = 0x0000000100008378
name = 0x0000000100003f27 “weight”
type = 0x0000000100003f87 “f”
alignment_raw = 2
size = 4
}

p $7.get(2):

(ivar_t) $10 = {
offset = 0x0000000100008380
name = 0x0000000100003f2e “_age”
type = 0x0000000100003f89 “i”
alignment_raw = 2
size = 4
}

p $7.get(3):

(ivar_t) $11 = {
offset = 0x0000000100008388
name = 0x0000000100003f33 “_name”
type = 0x0000000100003f60 “@”NSString””
alignment_raw = 3
size = 8
}

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

类方法在哪里

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

案例代码:


@interface LGPerson : NSObject

+ (void)eatFood;

@end

@implementation LGPerson

+ (void)eatFood{
    NSLog(@"%s",__func__);
}

@end
复制代码

lldb动态调试

获取类的类方法.png

步骤

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

p $7.get(0).big():

(method_t::big) $9 = {
name = “eatFood”
types = 0x0000000100003f74 “v16@0:8”
imp = 0x0000000100003ce0 (KCObjcBuild`+[LGPerson eatFood])
}

结论:类的类方法存在元类的methods里面,等同于类的类方法是元类的实例方法

补充:

小端模式

iOS是小端模式,内存上从右往左读取

高、低地址

高、低代表的是方向,高是左面、低是右面,通常以中间划分。例如一个8字节的64bit位,从左往右为高32位,从右往左位低32位

ULL

ULL:unsigned long long 无符号长整形

源码来源

我这里查看的源码是objc4-818.2.tar.gz,github.com/LGCooci/obj…,有需求的伙伴可以自行获取,素质三连

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