第5课-类的原理分析上-1

第5课-类的原理分析上-1

[TOC]

5.1 类的分析

5.1.1 元类

首先我们看一下下面实例,引出元类

ZBPerson继承NSObject

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

- (void)sayHello;
+ (void)say666;
@end

ZBTeacher继承ZBPerson

@interface ZBTeacher : ZBPerson
@end

接下来,我们进行lldb调试,如下图:

在上图中,我们有个疑问点? 为什么通过p/x 0x011d8001000082c5 & 0x0000000ffffffff8ULL与通过p/x 0x0000000100008298 & 0x0000000ffffffff8ULL获得的都是ZBPerson对象呢?

  • 0x011d8001000082c5是person对象isa首地址,所以0x011d8001000082c5 & 0x0000000ffffffff8ULL得到的即为person对象的isa指针关联的ZBPerson类
  • 0x0000000100008298是上面第一步获取的ZBPerson类的isa指针地址,那么它指向的是ZBPerson类的类,我们这里称之为ZBPerson的元类。
  • 所以上面之所以打印相同的值,是因为元类造成的。

接下来我们进一步探索元类。

首先我们来解释一下元类:

  • 我们都知道对象的isa是指向类,类其实也是一个对象,可以称为类对象,其isa指针指向苹果定义的元类
  • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
  • 元类是类对象的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。
  • 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称

5.1.2 isa走位以及类的继承关系图

接下来我们探索一下isa的走位以及类的继承关系

通过上面的分析我们可以得出如下结论:

  • 对象的isa走向(红色部分):
    1. 对象的isa指针->对象所对应类的地址
    2. 对象类的isa指针->元类对象的地址
    3. 元类对象的isa指针->NSObject根元类对象的地址
    4. NSObject根元类对象的isa指针依然指向NSObject元类对象自身。
  • NSObject类对象的isa指针走向(蓝色部分):
    1. NSObject类对象的isa指针->NSObject根元类对象的地址
    2. NSObject根元类对象的isa指针依然指向NSObject元类对象自身
  • 补充:
    • 对的isa指向类信息
    • 类的isa与元类地址相同

注意对象isa指针和NSObjcet类的isa指针最后都指向了NSObject根元类,而且二者指向的根元类地址相同,即NSObject(根元类)在内存中永远只存在一份

[面试题]:类存在几份? 由于类的信息在内存中永远只存在一份,所以 类对象只有一份

接下来我们再来看一下著名的isa走位以及继承关系图

isa走位关系以及类的继承关系如下: 子类isa走位

  • 子类实例对象(Instance of Subclass)的isa指向子类(Subclass(class))
  • 子类(Subclass(class))的isa指向子类的元类(Subclass(meta))
  • 子类的元类(Subclass(meta))的isa指向根元类(Root class (meta))
  • 根元类(Root class (meta))的isa指向根元类(Root class (meta))

代码验证:

// 继承关系:ZBTeacher -> ZBPerson -> NSObject
// ZBTeacher实例
ZBTeacher *tObject = [ZBTeacher alloc];
// ZBTeacher类
Class tClass = object_getClass(tObject);
// ZBTeacher元类
Class tMetaClass = object_getClass(tClass);
// ZBTeacher根元类
Class tRootClass = object_getClass(tMetaClass);
// ZBTeacher根元类的元类
Class tRootRootClass = object_getClass(tRootClass);
NSLog(@"\n%p ZBTeacher子类实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",tObject,tClass,tMetaClass,tRootClass,tRootRootClass);

// 打印
0x101206330 ZBTeacher子类实例对象
0x100008330 类
0x100008308 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类

父类isa走位

  • 父类实例对象(Instance of superclass)的isa指向父类(superclass(class))
  • 父类(superclass(class))的isa指向父类的元类(superclass(meta))
  • 父类的元类(superclass(meta))的isa指向根元类(Root class (meta))
  • 根元类(Root class (meta))的isa指向根元类(Root class (meta))

代码验证

// ZBPerson实例
ZBPerson *pObject = [ZBPerson alloc];
// ZBPerson类
Class pClass = object_getClass(pObject);
// ZBPerson元类
Class pMetaClass = object_getClass(pClass);
// ZBPerson根元类
Class pRootClass = object_getClass(pMetaClass);
// ZBPerson根元类的元类
Class pRootRootClass = object_getClass(pRootClass);
NSLog(@"\n%p ZBPerson父类实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",pObject,pClass,pMetaClass,pRootClass,pRootRootClass);

// 打印
0x101604720 ZBPerson父类实例对象
0x1000082e0 类
0x1000082b8 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类

根类(NSObject)走位

  • 根类实例对象(Instance of Root class)的isa指向根类(Root class(class))
  • 根类(Root class(class))的isa指向根元类(Root class(meta))
  • 根元类(Root class (meta))的isa指向根元类(Root class (meta))

代码验证:

// 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 NSObject实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass, rootRootMetaClass);

// 结果打印
0x10070d2a0 NSObject实例对象
0x7ff854c59d48 类
0x7ff854c59d20 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类

继承关系如下:

  • 子类(Subclass(class))===>继承===> 父类(superclass(class))===>继承===> NSObject根类(Root class(class) 代码验证:

      // ZBTeacher类的父类
      Class tFatherClass = class_getSuperclass(tClass);
      // ZBTeacher类的的祖父
      Class tGrandFatherClass = class_getSuperclass(tFatherClass);
      // ZBTeacher类的的曾祖
      Class tGrandGrandFatherClass = class_getSuperclass(tGrandFatherClass);
      NSLog(@"\n%p ZBTeacher类的父类\n%p ZBTeacher类的的祖父\n%p ZBTeacher类的的曾祖",tFatherClass,tGrandFatherClass,tGrandGrandFatherClass);
    
      // 结果打印
      0x1000082e0 ZBTeacher类的父类
      0x7ff854c59d48 ZBTeacher类的的祖父
      0x0 ZBTeacher类的的曾祖
      (lldb) po 0x0
      <nil>
  • 子类的元类(Subclass(meta)) ===>继承===> 父类的元类(superclass(meta)) ===>继承===> 根元类(Root class (meta)) ===>继承===> NSObject根类(Root class(class) 代码验证:

      // ZBTeacher元类的父类
      Class tMetaFatherClass = class_getSuperclass(tMetaClass);
      // ZBTeacher元类的的祖父
      Class tMetaGrandFatherClass = class_getSuperclass(tMetaFatherClass);
      // ZBTeacher元类的的曾祖
      Class tMetaGrandGrandFatherClass = class_getSuperclass(tMetaGrandFatherClass);
      NSLog(@"\n%p ZBTeacher元类的父类\n%p ZBTeacher元类的的祖父\n%p ZBTeacher元类的的曾祖",tMetaFatherClass,tMetaGrandFatherClass,tMetaGrandGrandFatherClass);
    
      // 结果打印
      0x1000082b8 ZBTeacher元类的父类
      0x7ff854c59d20 ZBTeacher元类的的祖父
      0x7ff854c59d48 ZBTeacher元类的的曾祖
      (lldb) po 0x7ff854c59d48
      NSObject
  • NSObject的父类为nil 代码验证:

      // NSObject 根类特殊情况
      // NSObject根类的父类
      Class objectSuperClass = class_getSuperclass(NSObject.class);
      // 根元类的父类
      Class rootMetaFatherClass = class_getSuperclass(rootMetaClass);
      NSLog(@"\n%p NSObject根类的父类\n%p 根元类的父类",objectSuperClass,rootMetaFatherClass);
    
      // 结果打印
      0x0 NSObject根类的父类
      0x7ff854c59d48 根元类的父类
  • 注意:对象与对象之间没有继承关系,只有类与类才有继承关系

所以 NSObject 是最终的根类,是万物之祖。

5.2 类结构分析

5.2.1 objc_class与objc_object的关系

我们直接查看一下objc4的源码,可以查看objc_class的源码有两份,其中一份 在objc-runtime-old.h文件中,这里的定义是老版本的,已经不是最新的代码了

另一份最新的代码在objc-runtime-new.h文件中 源代码如下:

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;

#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
        void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
        if ((void *)superclass == stripped) {
            void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
            if ((void *)superclass != resigned)
                return Nil;
        }
#endif

        void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
        return (Class)result;

#   else
        return (Class)ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
#   endif
#else
        return superclass;
#endif
    }

    void setSuperclass(Class newSuperclass) {
#if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
        superclass = (Class)ptrauth_sign_unauthenticated((void *)newSuperclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
#else
        superclass = newSuperclass;
#endif
    }

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

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        ASSERT((set & clear) == 0);
        data()->changeFlags(set, clear);
    }

#if FAST_HAS_DEFAULT_RR
    bool hasCustomRR() const {
        return !bits.getBit(FAST_HAS_DEFAULT_RR);
    }
    void setHasDefaultRR() {
        bits.setBits(FAST_HAS_DEFAULT_RR);
    }
    void setHasCustomRR() {
        bits.clearBits(FAST_HAS_DEFAULT_RR);
    }
#else
    bool hasCustomRR() const {
        return !(bits.data()->flags & RW_HAS_DEFAULT_RR);
    }
    void setHasDefaultRR() {
        bits.data()->setFlags(RW_HAS_DEFAULT_RR);
    }
    void setHasCustomRR() {
        bits.data()->clearFlags(RW_HAS_DEFAULT_RR);
    }
#endif

#if FAST_CACHE_HAS_DEFAULT_AWZ
    bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
#else
    bool hasCustomAWZ() const {
        return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

#if FAST_CACHE_HAS_DEFAULT_CORE
    bool hasCustomCore() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
    }
    void setHasDefaultCore() {
        return cache.setBit(FAST_CACHE_HAS_DEFAULT_CORE);
    }
    void setHasCustomCore() {
        return cache.clearBit(FAST_CACHE_HAS_DEFAULT_CORE);
    }
#else
    bool hasCustomCore() const {
        return !(bits.data()->flags & RW_HAS_DEFAULT_CORE);
    }
    void setHasDefaultCore() {
        bits.data()->setFlags(RW_HAS_DEFAULT_CORE);
    }
    void setHasCustomCore() {
        bits.data()->clearFlags(RW_HAS_DEFAULT_CORE);
    }
#endif

#if FAST_CACHE_HAS_CXX_CTOR
    bool hasCxxCtor() {
        ASSERT(isRealized());
        return cache.getBit(FAST_CACHE_HAS_CXX_CTOR);
    }
    void setHasCxxCtor() {
        cache.setBit(FAST_CACHE_HAS_CXX_CTOR);
    }
#else
    bool hasCxxCtor() {
        ASSERT(isRealized());
        return bits.data()->flags & RW_HAS_CXX_CTOR;
    }
    void setHasCxxCtor() {
        bits.data()->setFlags(RW_HAS_CXX_CTOR);
    }
#endif

#if FAST_CACHE_HAS_CXX_DTOR
    bool hasCxxDtor() {
        ASSERT(isRealized());
        return cache.getBit(FAST_CACHE_HAS_CXX_DTOR);
    }
    void setHasCxxDtor() {
        cache.setBit(FAST_CACHE_HAS_CXX_DTOR);
    }
#else
    bool hasCxxDtor() {
        ASSERT(isRealized());
        return bits.data()->flags & RW_HAS_CXX_DTOR;
    }
    void setHasCxxDtor() {
        bits.data()->setFlags(RW_HAS_CXX_DTOR);
    }
#endif

#if FAST_CACHE_REQUIRES_RAW_ISA
    bool instancesRequireRawIsa() {
        return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
    }
    void setInstancesRequireRawIsa() {
        cache.setBit(FAST_CACHE_REQUIRES_RAW_ISA);
    }
#elif SUPPORT_NONPOINTER_ISA
    bool instancesRequireRawIsa() {
        return bits.data()->flags & RW_REQUIRES_RAW_ISA;
    }
    void setInstancesRequireRawIsa() {
        bits.data()->setFlags(RW_REQUIRES_RAW_ISA);
    }
#else
    bool instancesRequireRawIsa() {
        return true;
    }
    void setInstancesRequireRawIsa() {
        // nothing
    }
#endif
    void setInstancesRequireRawIsaRecursively(bool inherited = false);
    void printInstancesRequireRawIsa(bool inherited);

#if CONFIG_USE_PREOPT_CACHES
    bool allowsPreoptCaches() const {
        return !(bits.data()->flags & RW_NOPREOPT_CACHE);
    }
    bool allowsPreoptInlinedSels() const {
        return !(bits.data()->flags & RW_NOPREOPT_SELS);
    }
    void setDisallowPreoptCaches() {
        bits.data()->setFlags(RW_NOPREOPT_CACHE | RW_NOPREOPT_SELS);
    }
    void setDisallowPreoptInlinedSels() {
        bits.data()->setFlags(RW_NOPREOPT_SELS);
    }
    void setDisallowPreoptCachesRecursively(const char *why);
    void setDisallowPreoptInlinedSelsRecursively(const char *why);
#else
    bool allowsPreoptCaches() const { return false; }
    bool allowsPreoptInlinedSels() const { return false; }
    void setDisallowPreoptCaches() { }
    void setDisallowPreoptInlinedSels() { }
    void setDisallowPreoptCachesRecursively(const char *why) { }
    void setDisallowPreoptInlinedSelsRecursively(const char *why) { }
#endif

    bool canAllocNonpointer() {
        ASSERT(!isFuture());
        return !instancesRequireRawIsa();
    }

    bool isSwiftStable() {
        return bits.isSwiftStable();
    }

    bool isSwiftLegacy() {
        return bits.isSwiftLegacy();
    }

    bool isAnySwift() {
        return bits.isAnySwift();
    }

    bool isSwiftStable_ButAllowLegacyForNow() {
        return bits.isSwiftStable_ButAllowLegacyForNow();
    }

    uint32_t swiftClassFlags() {
        return *(uint32_t *)(&bits + 1);
    }

    bool usesSwiftRefcounting() {
        if (!isSwiftStable()) return false;
        return bool(swiftClassFlags() & 2); //ClassFlags::UsesSwiftRefcounting
    }

    bool canCallSwiftRR() {
        // !hasCustomCore() is being used as a proxy for isInitialized(). All
        // classes with Swift refcounting are !hasCustomCore() (unless there are
        // category or swizzling shenanigans), but that bit is not set until a
        // class is initialized. Checking isInitialized requires an extra
        // indirection that we want to avoid on RR fast paths.
        //
        // In the unlikely event that someone causes a class with Swift
        // refcounting to be hasCustomCore(), we'll fall back to sending -retain
        // or -release, which is still correct.
        return !hasCustomCore() && usesSwiftRefcounting();
    }

    bool isStubClass() const {
        uintptr_t isa = (uintptr_t)isaBits();
        return 1 <= isa && isa < 16;
    }

    // Swift stable ABI built for old deployment targets looks weird.
    // The is-legacy bit is set for compatibility with old libobjc.
    // We are on a "new" deployment target so we need to rewrite that bit.
    // These stable-with-legacy-bit classes are distinguished from real
    // legacy classes using another bit in the Swift data
    // (ClassFlags::IsSwiftPreStableABI)

    bool isUnfixedBackwardDeployingStableSwift() {
        // Only classes marked as Swift legacy need apply.
        if (!bits.isSwiftLegacy()) return false;

        // Check the true legacy vs stable distinguisher.
        // The low bit of Swift's ClassFlags is SET for true legacy
        // and UNSET for stable pretending to be legacy.
        bool isActuallySwiftLegacy = bool(swiftClassFlags() & 1);
        return !isActuallySwiftLegacy;
    }

    void fixupBackwardDeployingStableSwift() {
        if (isUnfixedBackwardDeployingStableSwift()) {
            // Class really is stable Swift, pretending to be pre-stable.
            // Fix its lie.
            bits.setIsSwiftStable();
        }
    }

    _objc_swiftMetadataInitializer swiftMetadataInitializer() {
        return bits.swiftMetadataInitializer();
    }

    // Return YES if the class's ivars are managed by ARC, 
    // or the class is MRC but has ARC-style weak ivars.
    bool hasAutomaticIvars() {
        return data()->ro()->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC);
    }

    // Return YES if the class's ivars are managed by ARC.
    bool isARC() {
        return data()->ro()->flags & RO_IS_ARC;
    }


    bool forbidsAssociatedObjects() {
        return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
    }

#if SUPPORT_NONPOINTER_ISA
    // Tracked in non-pointer isas; not tracked otherwise
#else
    bool instancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        ASSERT(isFuture()  ||  isRealized());
        return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        ASSERT(isFuture()  ||  isRealized());
        setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }
#endif

    bool shouldGrowCache() {
        return true;
    }

    void setShouldGrowCache(bool) {
        // fixme good or bad for memory use?
    }

    bool isInitializing() {
        return getMeta()->data()->flags & RW_INITIALIZING;
    }

    void setInitializing() {
        ASSERT(!isMetaClass());
        ISA()->setInfo(RW_INITIALIZING);
    }

    bool isInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }

    void setInitialized();

    bool isLoadable() {
        ASSERT(isRealized());
        return true;  // any class registered for +load is definitely loadable
    }

    IMP getLoadMethod();

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized() const {
        return !isStubClass() && (data()->flags & RW_REALIZED);
    }

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture() const {
        if (isStubClass())
            return false;
        return data()->flags & RW_FUTURE;
    }

    bool isMetaClass() const {
        ASSERT_THIS_NOT_NULL;
        ASSERT(isRealized());
#if FAST_CACHE_META
        return cache.getBit(FAST_CACHE_META);
#else
        return data()->flags & RW_META;
#endif
    }

    // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
        static_assert(RO_META == RW_META, "flags alias");
        if (isStubClass())
            return false;
        return data()->flags & RW_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClassMaybeUnrealized()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return getSuperclass() == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    // If this class does not have a name already, we can ask Swift to construct one for us.
    const char *installMangledNameForLazilyNamedClass();

    // Get the class's mangled name, or NULL if the class has a lazy
    // name that hasn't been created yet.
    const char *nonlazyMangledName() const {
        return bits.safe_ro()->getName();
    }

    const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT_THIS_NOT_NULL;

        const char *result = nonlazyMangledName();

        if (!result) {
            // This class lazily instantiates its name. Emplace and
            // return it.
            result = installMangledNameForLazilyNamedClass();
        }

        return result;
    }

    const char *demangledName(bool needsLock);
    const char *nameForLogging();

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceStart() const {
        ASSERT(isRealized());
        return data()->ro()->instanceStart;
    }

    // Class's instance start rounded up to a pointer-size boundary.
    // This is used for ARC layout bitmaps.
    uint32_t alignedInstanceStart() const {
        return word_align(unalignedInstanceStart());
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

    inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

    void setInstanceSize(uint32_t newSize) {
        ASSERT(isRealized());
        ASSERT(data()->flags & RW_REALIZING);
        auto ro = data()->ro();
        if (newSize != ro->instanceSize) {
            ASSERT(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&ro->instanceSize) = newSize;
        }
        cache.setFastInstanceSize(newSize);
    }

    void chooseClassArrayIndex();

    void setClassArrayIndex(unsigned Idx) {
        bits.setClassArrayIndex(Idx);
    }

    unsigned classArrayIndex() {
        return bits.classArrayIndex();
    }
};

通过分析源码,我们能够发现objc_class

  • objc_class是一个结构体,继承自objc_object
  • objc_class内部包含了4个成员变量和众多方法
    • 从objc_object继承过过来Class isa
    • Class superclass: 父类
    • cache_t cache
    • class_data_bits_t bits;

我们再来看一下objc_object的源码,我们发现objc_object也有两份 其中一份位于objc-private.h中

另一份位于objc.h中

还记得在探索OC对象原理的一篇中,我们通过编译器,将源码编译之后得到 其中NSObject就被编译成了结构体NSObject_IMPL,结构体NSObject_IMPL就与这里objc_object定义是相同的,所以苹果应该是使用的这里的objc_object。

【问题】NSObject、Class、objc_class 与 objc_object 有什么关系?

  • NSObject对象(比如person,注意这里person是对象,NSObjcet是类,person是NSObject的实例)在底层的本质就是objc_object结构体 NSObject对象编译之后会变成 里面有一个Class类型的isa变量,而Class则是一个objc_class的指针类型,而objc_class又继承自objc_object。
  • Class的本质在底层就是一个objc_class结构体指针typedef struct objc_class *Class;
  • objc_class继承自objc_object,二者之间是继承关系
  • objc_object 与 NSObject对象的关系是同一个事物的不同表现形式,在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_object结构体类型

通过上面的分析,我们可以总结如下:

  1. 所有的对象 + 类 + 元类 + 根元类 都有isa属性,即对象和类都有isa属性
  2. 在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_object结构体类型;而Class在底层其实就是一个objc_class的结构体指针
  3. 在OC底层通过结构体定义了模板,例如objc_class、objc_objec
    1. 所有以 objc_object为模板 创建的对象,都有isa属性
    2. 所有以objc_class为模板,创建的类,都有isa属性

objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图所示

【百度面试题】objc_object 与 对象的关系

  • objc_object 与 NSObject对象的关系是同一个事物的不同表现形式,在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_objectC/C++)结构体类型
  • 所以可以说所有的对象在底层都是以objc_object为模板创建出来的

5.2.2 计算cache类的内存大小

在类原理基础分析一篇中,我们知道一个数组成员变量的地址是可以通过内存偏移计算,这里结构体同样使用。

通过上文的分析我们知道objc_object主要包含以下成员变量

struct objc_class : objc_object {
    // Class ISA; //8字节
    Class superclass; //Class 类型 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    //....方法部分省略,未贴出
}
  • isa指针: 指针类型,8字节
  • Class superclass:Class也是结构体指针类型,8字节
  • cache_t cache;: 我们无法直接看出cache_t的大小
  • bits:要想获取bits的首地址,我们只需要将类地址平移(isa+superclass+cache所占的总字节数),即可以得到bits的首地址

目前我们已经知道了isa和superclass的大小,接下来我们计算一下cache的大小。cache是cache_t类型,首先我们看一下cache_t的源码:

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

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;

    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
    static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;

    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;

    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
            "Bucket field doesn't have enough bits for arbitrary pointers.");

#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
    // 63..60: hash_mask_shift
    // 59..55: hash_shift
    // 54.. 1: buckets ptr + auth
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        uintptr_t value = (uintptr_t)cache->shift << 55;
        // masks have 11 bits but can be 0, so we compute
        // the right shift for 0x7fff rather than 0xffff
        return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
    }
#else
    // 63..53: hash_mask
    // 52..48: hash_shift
    // 47.. 1: buckets ptr
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        return (uintptr_t)cache->hash_params << 48;
    }
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
    // _maybeMask is unused, the mask length is stored in the low 4 bits

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif

    bool isConstantEmptyCache() const;
    bool canBeFreed() const;
    mask_t mask() const;

#if CONFIG_USE_PREOPT_CACHES
    void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
    const preopt_cache_t *disguised_preopt_cache() const;
#endif

    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);

    static bucket_t *emptyBuckets();
    static bucket_t *allocateBuckets(mask_t newCapacity);
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));

public:
    // The following four fields are public for objcdt's use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn't care for locks and pesky little details like this
    // and can safely use these.
    unsigned capacity() const;
    struct bucket_t *buckets() const;
    Class cls() const;

#if CONFIG_USE_PREOPT_CACHES
    const preopt_cache_t *preopt_cache(bool authenticated = true) const;
#endif

    mask_t occupied() const;
    void initializeToEmpty();

#if CONFIG_USE_PREOPT_CACHES
    bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
    bool shouldFlush(SEL sel, IMP imp) const;
    bool isConstantOptimizedCacheWithInlinedSels() const;
    Class preoptFallbackClass() const;
    void maybeConvertToPreoptimized();
    void initializeToEmptyOrPreoptimizedInDisguise();
#else
    inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
    inline bool shouldFlush(SEL sel, IMP imp) const {
        return cache_getImp(cls(), sel) == imp;
    }
    inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
    inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif

    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);

    static void init();
    static void collectNolock(bool collectALot);
    static size_t bytesForCapacity(uint32_t cap);

#if __LP64__
    bool getBit(uint16_t flags) const {
        return _flags & flags;
    }
    void setBit(uint16_t set) {
        __c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
    }
    void clearBit(uint16_t clear) {
        __c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
    }
#endif

#if FAST_CACHE_ALLOC_MASK
    bool hasFastInstanceSize(size_t extra) const
    {
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        }
        return _flags & FAST_CACHE_ALLOC_MASK;
    }

    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

    void setFastInstanceSize(size_t newSize)
    {
        // Set during realization or construction only. No locking needed.
        uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
        uint16_t sizeBits;

        // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
        // to yield the proper 16byte aligned allocation size with a single mask
        sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
        sizeBits &= FAST_CACHE_ALLOC_MASK;
        if (newSize <= sizeBits) {
            newBits |= sizeBits;
        }
        _flags = newBits;
    }
#else
    bool hasFastInstanceSize(size_t extra) const {
        return false;
    }
    size_t fastInstanceSize(size_t extra) const {
        abort();
    }
    void setFastInstanceSize(size_t extra) {
        // nothing
    }
#endif
};

上面的代码可以简化为:

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;
    };
    // 一系列static变量和方法
    ....
    // 一系列方法
    ...

我们知道方法是不占用类的内存空间的,而且static静态变量也不是存储在类内存中的,而是存储到全局的静态存储区。也就是说cache_t的成员变量主要有2个:

  • explicit_atomic _bucketsAndMaybeMask ,占用8字节 其中explicit_atomic源码如下:

      template <typename T>
      struct explicit_atomic : public std::atomic<T> {
          explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {}
          operator T() const = delete;
    
          T load(std::memory_order order) const noexcept {
              return std::atomic<T>::load(order);
          }
          void store(T desired, std::memory_order order) noexcept {
              std::atomic<T>::store(desired, order);
          }
    
          // Convert a normal pointer to an atomic pointer. This is a
          // somewhat dodgy thing to do, but if the atomic type is lock
          // free and the same size as the non-atomic type, we know the
          // representations are the same, and the compiler generates good
          // code.
          static explicit_atomic<T> *from_pointer(T *ptr) {
              static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *),
                            "Size of atomic must match size of original");
              explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr;
              ASSERT(atomic->is_lock_free());
              return atomic;
          }
      };

    它是c++中的泛型,字节占用的大小就是所传入类型的大小,这里传入的是uintptr_t,而uintptr_r是无符号长整型(typedef unsigned long uintptr_t;)占用8字节

  • 联合体union,占用8字节 联合体源码如下:

      union {
              struct {
                  explicit_atomic<mask_t>    _maybeMask;
      #if __LP64__
                  uint16_t                   _flags;
      #endif
                  uint16_t                   _occupied;
              };
              explicit_atomic<preopt_cache_t *> _originalPreoptCache;
          };

    其中:

    • 结构体最大8字节
      • _maybeMask:是mask_t(typedef uint32_t mask_t;, typedef unsigned int uint32_t;)无符号整型的大小,占用4字节
      • _flags:是uint16_t(typedef unsigned short uint16_t;)无符号short类型,占用2字节
      • _occupied:是uint16_t(typedef unsigned short uint16_t;)无符号short类型,占用2字节
    • _originalPreoptCache:是preopt_cache_t *指针类型,占用8字节
    • 因为联合体是公用一块内存,所以联合体总共占用8字节。

结论综上所述cache_t总结占用8+8=16字节大小

5.2.3 获取bits

通过上面的分析我们知道了bits前面的3个成员变量总计占用了32字节的大小。根据内存平移原则 bits地址=类首地址+32字节

class_data_bits_t bits;bits是class_data_bits_t类型,class_data_bits_t的源码如下:

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

};

由此可见它是一个结构体类型,其中里面有一个data函数,该函数返回一个class_rw_t*指针

class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

其中class_rw_t也是一个结构体,而且我们常见的方法列表methods,属性列表properties,协议列表protocols都存储在这个结构体中,其源码如下:

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

所以我们可以通过以下方式获取类的方法、属性和协议:

  1. 首先通过类的首地址+32字节得到bits的首地址(class_data_bits_t类型)

  2. 然后通过调用bits.data()函数获取到class_rw_t指针

  3. 接着通过class_rw_t指针,调用methods函数获取类的方法列表;调用properties函数获取属性列表;调用protocols函数获取协议列表

5.2.4 属性列表 property_list在底层的存储

接下来我们通过一个实例获取property_list。代码如下:

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

- (void)sayHello;
+ (void)say666;
@end
@implementation ZBBaseTeacher
- (void)sayHello
{

}
+ (void)say666
{

}
@end


@interface ZBTeacher : ZBBaseTeacher
@end
@implementation ZBTeacher
@end

我们在main函数中调试ZBBaseTeacher对象,通过lldb调试如下:

  1. 第一步我们需要先获取ZBBaseTeacher类在内存中的信息,在类中:
    1. isa指针,占用8字节
    2. superclass指针,占用8字节
    3. cache结构体,占用16字节
  2. 第2步我们通过内存偏移32字节获取bits的首地址,我们可以有两种方式来计算:
    1. 通过类的首地址+0x20,例如上图中:p/x 0x100008308+0x20
    2. 直接通过第一步中口算得到0x100008328地址,这就是bits的首地址
  3. 我们在第2步获取的是bits的首地址指针,所以为了后续的操作方便,我们这里进行强转一下,将bits地址指针转换成class_data_bits_t *类型。
    1. 注意,在lldb环境进行强转时,需要在objc源码环境,如果不是源码环境,会报找不到class_data_bits_t类型的错误
  4. 调用class_data_bits_t的data()函数获取数据
    1. data函数源码如下,返回值类型为class_rw_t*类型
    2. 另外这里需要注意一下,第3步我们获得的是class_data_bits_t指针,所以访问data函数,需要使用p $2->data(),如果此时的$2是结构体类型,那么可以直接通过p $2.data()来访问
  5. 打印class_rw_t指针指向的具体数据,也就是获取到class_rw_t结构体
  6. 通过调用class_rw_t结构体的properties方法获取具体属性
    1. properties源码如下
    2. 方法的返回值类型为property_array_t,源码如下: 可以发现这是一个数组中还包含一个list数组property_list_t,这才是我们属性真正的藏身之处。
    3. 再进一步点开property_list_t,发现是一个空结构体
    4. 再进一步点开property_t,可以发现它也是一个结构体,正是我们要找的属性,源码如下:
  7. 我们通过p $5.list.ptr获取到property_list_t列表的指针地址
  8. 通过*$6直接访问property_list_t列表的数据
  9. 直接调用get(index) 来访问具体的属性,如果index越界之后会报错。

注意: 1. 我们第一步获取的一定是一个类的内存,而不是对象,如果对对象进行操作会报错,而且也分析不到具体的数据

  1. 如果我们使用的M1芯片的电脑,那么由于架构不同的原因,那么最后一步需要调用get(index).getDescription

结论:对象的属性列表存储在类的bits字段中, 通过bits.data()->properties()---> list ---> ptr ---> get(index)获取

5.2.5 成员变量列表在底层的存储

通过上面的分析,我们发现还有一个问题。 问题:ZBBaseTeacher还有一个hobby的成员变量,为什么这里获取不到呢?

我们在上面的分析过程中,可以知道class_rw_t结构体中有methods、properties、protocols等方法,那么同样应该也有获取成员变量的方法,我们寻找一番会找到如下函数: 那么是不是通过此方法来获取成员变量呢,我们通过lldb验证一下

验证通过。其中需要注意以下几点:

  1. 调用class_rw_t的ro函数会得到一个class_ro_t的指针。
    1. class_ro_t也是一个结构体,源码如下 其中有一个ivars变量用来保存成员变量,它是一个ivar_list_t的指针类型
    2. ivar_list_t也是一个结构体,源码如下:
    3. 这里的ivar就是我们的成员变量,它是Ivar类型,而Ivar类型就是一个结构体指针类型 里面包含了名字name,类型type,以及所占用字节数size等关键信息
    4. class_rw_t与class_ro_t的区别参照:https://www.jianshu.com/p/823eaedb3697 苹果官方视频:https://developer.apple.com/videos/play/wwdc2020/10163/
  2. 打印class_ro_t的具体内容
  3. 第3-4步就是为了获取ivar_list_t的具体内容
  4. 第5步通过get方法获取成员变量的内容,通过打印结果我们可以发现
    1. 成员变量hobby,字符串类型,占8字节
    2. 成员变量_name,字符串类型,占8字节
    3. 成员变量_nickName,字符串类型,占8字节

结论:对象的成员变量列表存储在类的bits字段中, 通过bits.data()->ro()---> ivars ---> get(index)获取

5.2.6 对象方法列表 methods_list在底层的存储

通过上面的分析我们知道了方法列表位域methods方法中,我们直接验证

这里我们发现了一个问题,我们通过get(index) 函数打印的时候,结果发现全是空的,那么是怎么回事呢?

我们找到mehods函数源码

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

它返回一个method_array_t类型 我们点开method_t的源码发现里面有个big结构体,里面存储的正是我们方法的信息

  • name,SEL类型,方法签名
  • types,const char *类型,方法类型
  • imp,MethodListIMP(using MethodListIMP = IMP __ptrauth_objc_method_list_imp;)类型,方法实现,这里就是一个函数指针

接下我们再验证一下: 验证没问题,上面打印了5个方法,唯独缺少一个类方法say666

结论:对象方法列表存储在类的bits字段中, 通过bits.data()->methods()---> list ---> ptr ---> get(index).big()获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值