OC类的原理之类的本质

414 阅读7分钟

类的结构

  • 首先看下面代码的底层实现:
@interface LGPerson : NSObject
@property (nonatomic) NSString *name;
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson  *p = [LGPerson alloc];
        NSLog(@"p:%@", p);
    }
    return 0;
}
  • 使用clang编译:clang -rewrite-objc main.m -o main.cpp
// ...
struct objc_class;
typedef struct objc_class *Class;
// ...
struct NSObject_IMPL {
	Class isa;
};
// ...
typedef struct objc_object LGPerson;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
// ...
  • 从编译后的结果可以看出:(这里省略了和本文分析无关的代码)
    • LGPerson类实际上是struct objc_object类型
    • LGPerson的具体实现是LGPerson_IMPL,第一个成员是NSObject_IMPL中的isa
    • isa的类型是ClassClass的类型是struct objc_class
    • objc_class在这里只有声明,并没有具体定义,我们可以看下objc源码中是否有相关定义
  • objc源码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
    // ...
}

struct objc_object {
    isa_t isa;
}
  • objc源码可以看到,objc_class继承自objc_object,第一个成员变量是isa
  • 在前面的文章我们分析过了对象的isa指向类,那么类的isa指向什么呢?

类的isa

  • 通过下面的代码,看下类的isa指向
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson  *p = [LGPerson alloc];
    }
    return 0;
}
  • LLDB打印isa信息:
    • x/4gx:打印内存中存储的数据
    • p/x:以16进制形式输出
    • 0x00007ffffffffff8:是ISA_MASK的值,&该值的目的是取出NONPOINTER_ISA中的isa

image.png

  • 从上面的打印结果可以看出
    • 实例变量的isa指向类LGPerson
    • LGPerson类的isa指向另一个LGPerson类(这两个LGPerson类不是同一个,因为他们的地址不同,实际上第二个LGPerson类是元类)
    • 打印LGPerson类的内存地址,通过此验证得知,对象的isa指向类,类的isa指向元类

类的关系

  • 基本概念
    • 根类:在OC中几乎所有的类都继承自NSObject类,NSObject类就是根类,根类的父类为nil
    • 元类:在我们平时开发中会用到类方法和实例方法,但是在底层的实现中并没有这种区分,实际上都是通过实例方法去查找的,底层为了区分这两种方法,引入元类的概念,然后将实例方法存储在类中,类方法存储在元类中。类的isa指向元类。
    • 根元类:即为根类NSObjectisa指向的类
  • 关系图

isa流程图.png

  • 总结:
    • isa的指向:
      • 实例变量的isa指向对应的类
      • 类的isa指向对应的元类
      • 元类的isa指向根元类
      • 根元类的isa指向自身
    • 类的继承:
      • 类的superclass指向父类
      • 父类的superclass指向根类
      • 根类的superclass指向nil
    • 元类的继承:
      • 元类的superclass指向对应类的父类的元类
      • 父类的元类的superclass指向根元类
      • 根元类的superclass指向根类
      • 根类的superclass指向nil

补充:结构体内存平移取值

  • 举例:
struct LGStruct {
    int  a; // 偏移:0, 占4字节
    char b; // 偏移:4, 占1字节
    int  c; // 偏移:8, 占4字节
    long d; // 偏移:16,占8字节(成员变量的开始位置为当前成员变量所占内存的整数倍)
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct LGStruct s;
        s.a = 10;
        s.b = 'a';
        s.c = 30;
        s.d = 40;
    }
    return 0;
}
  • 取出某个成员变量的值:
    • 先确定结构体变量地址
    • 确定成员变量的偏移位置
    • 计算成员变量的内存地址:结构体变量地址+该变量偏移位置
    • 将计算得到的地址转为该变量对应的类型的指针类型(例如aint类型,则将计算的地址转为int *类型,这样转的目的是取出int类型的值)

image.png

类的存储

  • 通过前面的分析,我们对类之间的关系和类的结构有了一个大致的了解,但是我们还不是很清楚类在内存中具体是如何存储的?
    • 属性存储在哪里?
    • 成员变量存储在哪里?
    • 方法存储在哪里?
  • 下面我们通过LLDB结合源码的方式分析下类在内存中的存储

类的bits分析

  • 以下的调试分析需要用到objc源码工程,本文调试的源码地址:gitee.com/jrcode/ios_…
  • LGPerson定义:
@interface LGPerson : NSObject {
    NSString *subject;
}

@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)say666;

@end

@implementation LGPerson
- (void)sayHello {}
+ (void)say666 {}
@end
  • 从上面类的结构可以看出,类中主要有4个成员变量:
    • Class ISA;:isa指针
    • Class superclass;:父类
    • cache_t cache;:缓存相关
    • class_data_bits_t bits;
  • 从类的成员变量可以看出,类中的数据应该都存储在bits中,下面重点分析下bits
  • 首先看下cache_t的定义:
struct cache_t {
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;	// 4字节(uint32_t)
#if __LP64__	
            uint16_t                   _flags;		// 2字节
#endif
            uint16_t                   _occupied; 	// 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
    // 静态变量和方法是不占用内存的
}
  • cache_t的定义可以看出:
    • 第一个成员变量_bucketsAndMaybeMaskuintptr_t类型,实际为unsigned long类型,占8字节
    • 第二个成员变量为联合体(联合体成员共享同一块内存)
      • 联合体的第一个成员为结构体,最大占8字节
      • 联合体的第二个成员为指针类型,占8字节
      • 所以,联合体占内存大小即为最大成员占内存大小,即为8字节
    • 所以,cache_t占内存为16字节
  • bits数据的获取:
    • 通过前面介绍的结构体内存平移取值的方法可以获取bits的值
    • 在获取bits的值之前需要先确定bits的偏移量
    • 前两个成员变量ISAsuperclass都是Class类型,而Classstructobjc_class *类型,占8字节
    • 第三个成员变量cachecache_t类型,占16字节
    • 所以,成员变量bits的偏移地址为32字节(可以通过结构体内存地址+0x20计算得到)
  • class_data_bits_t定义:
struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
	// ...
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // ...
};
  • class_data_bits_t的定义可以看出,可以通过data()获取数据,结果为class_rw_t类型
  • class_rw_t的定义:
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;

	// ...

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    // ...

    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};
        }
    }
    // ...
};
  • class_rw_t的定义可以看出几个重要的方法:
    • methods()
    • properties()
    • ro()
  • 下面通过LLDB调试验证一下,这里是不是和我们猜想的一样存放的是对应的数据

读取class_rw_t数据

image.png

  • 去取LGPerson类在内存中的地址
  • 通过结构体内存平移获取bits
  • 获取bits中存储的class_rw_t类型数据地址
  • 查看class_rw_t中存储的具体数据

属性的存储(property_t)

image.png

  • p $2->properties():获取属性信息,返回property_array_t类型数据,继承自list_array_tt
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
	// ...
    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };
    // ...
};
  • p $8.list.ptr:获取property_array_t中存储的property_list_t数据
    • 从上面的list_array_tt的定义可以看出,这里的listList类型数据,即为传入的property_list_t类型数据
  • 查看property_list_t存储数据,property_list_t继承自entsize_list_tt
    • 这里定义了一个get方法,调用get最终会调用getOrEnd,返回的数据类型为Element
    • Element的类型即为传入的property_t类型
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
	// ...
    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
	// ...
};
  • 通过get方法获取property_t类型数据:
struct property_t {
    const char *name;
    const char *attributes;
};

对象方法的存储(method_t)

image.png

  • p $2->methods():获取方法信息,返回method_array_t类型数据,继承自list_array_tt
  • p $12.list.ptr:获取method_array_t中存储的method_list_t数据
  • 查看method_list_t存储数据,method_list_t继承自entsize_list_tt
    • Element的类型即为传入的method_t类型
  • 通过get方法获取method_t类型数据,这里打印{},可以看下method_t定义:
    • 通过定义可以看出,method_t需要通过big()取出数据
struct method_t {
    // ...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
	// ...
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
	// ...
};
  • 分别打印sayHello和属性name对应的gettersetter方法信息

成员变量的存储(ivar_t)

image.png

  • p $2->ro():获取ro信息,返回class_ro_t类型数据,class_ro_t定义:
    • 此结构体中包含ivar_list_t类型的成员变量ivars
    • ivars中存储我们定义的成员变量数据
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;

    // ...
};
  • p $19->ivars:获取成员变量信息,返回ivar_list_t类型数据
  • 查看ivar_list_t存储数据,ivar_list_t继承自entsize_list_tt
    • Element的类型即为传入的ivar_t类型
  • 通过get方法获取ivar_t类型数据:
struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;
	// ...
};

类方法的存储(method_t)

  • 类方法存储在元类中

image.png

  • 通过x/4gx LGPerson.class:查看LGPerson类的内存结构
  • 通过ISA_MASK的值获取类LGPerson类的isa,即找到LGPerson类对应的元类
  • 通过元类的地址,经过内存平移得到元类的bits内存地址
  • 通过data()获取bits中存储的数据

image.png

  • 通过methods()获取元类的bits中存储的方法列表数据
  • p $12.list.ptr:获取method_array_t中存储的method_list_t数据
  • 查看method_list_t存储数据,method_list_t继承自entsize_list_tt
    • Element的类型即为传入的method_t类型
  • 通过get方法和big()获取method_t类型数据,输出say666方法信息,即类方法存储在元类中