底层原理-04-类的探究

1.类的关系探究

  • 类是否是具有唯一性
    Class class1 = [KBSon class];
    Class class2 = [KBSon alloc].class;
    Class class3 = object_getClass([KBSon alloc]);
    Class class4 = [KBSon class];
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
复制代码

打印后地址都是 0x100008400-0x100008400-0x100008400-0x100008400说明在我们内存中KBSon应该是唯一的,类似我们开发中 单例的概念。

  • 类的继承

我们在iOS开发中经常使用继承,子类可以使用父类的方法和属性。

    KBSon *son = [KBSon alloc];
    KBFather  *father = [KBFather alloc];
    NSLog(@"%@-%@",son,father);
    NSLog(@"%@",class_getSuperclass(son.class));
    NSLog(@"%@",class_getSuperclass(father.class));
    NSLog(@"%@",class_getSuperclass(NSObject.class));
复制代码

打印后发现KBSon -> KBFather -> NSObject -> null它们的继承关系如下,
说明 NSObject就是最根本的类没有父类,类似 亚当和夏娃。在oc中所有的类都是继承于NSObject

截屏2021-06-18 下午2.15.21.png

  • isa走向

我们在新的方法中探究下 son的isa走位

KBSon *son = [[KBSon alloc]init];
    
    Class selfClass = object_getClass(son.class);
    
    Class superClass = object_getClass(selfClass);
    
    Class superSuperClass = object_getClass(superClass);
    
    Class rootClass = object_getClass(NSObject.class);
    NSLog(@"\n%@-%p 实例对象\n%@-%p 类\n%@-%p 元类\n%@-%p 根元类\n%@-%p 根根元类",son,son,selfClass,selfClass,superClass,superClass,superSuperClass,superSuperClass,rootClass,rootClass);
复制代码

打印发现

<KBSon: 0x10067f800>-0x10067f800 实例对象
KBSon-0x1000083f0NSObject-0x7fff88b97ca0 元类
NSObject-0x7fff88b97ca0 根元类
NSObject-0x7fff88b97ca0 根根元类
复制代码

对应的关系如下

截屏2021-06-18 下午3.00.30.png

  • 元类的继承

我们继续看下son superClass

  
    Class sMetaClass = object_getClass(KBSon.class);
    Class sSuperMetaClass = class_getSuperclass(sMetaClass);
    Class sRootMetaClass = class_getSuperclass(sSuperMetaClass);
    Class sRootSuperMetaClass = class_getSuperclass(sRootMetaClass);
    Class lastMetaClass = class_getSuperclass(sRootSuperMetaClass);
    NSLog(@"%@ - %@ - %@-%@- %@",sMetaClass,sSuperMetaClass,sRootMetaClass,sRootSuperMetaClass,lastMetaClass);
复制代码

打印后

2021-06-18 15:40:02.117330+0800 类的原理分析[36495:1702498] KBSon - KBFather - NSObject-NSObject- (null)

复制代码

他们的关系如下

截屏2021-06-18 下午4.12.09.png

最终得到了isa和继承关系

isa流程图.png

2.类的构成探究

2.1 类的源码分析

之前我们使用终端使用Clang编译器命令编译 探究我们我们知道类是类似

struct NSObject_IMPL {
	Class isa;
};
复制代码

isa 就是类的地址。我们通过源码分析可知
结构体中包含一个isa指针,指向 object_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 *` */
复制代码

OBJC2_UNAVAILABLE表明2.0以后在使用,废弃了。使用新的struct objc_class
objc-runtime-new 中查看

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
    ...下面有一些方法具体参考源码

}
复制代码
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA(bool authenticated = false);

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;
      ...下面有一些方法具体参考源码
    }
    
复制代码
  • ISA:继承自objc_object,指向元类。
  • superclass:指向父类。
  • cache:方法缓存,当调用一次方法后就会缓存进vtable中,加速下次调用。
  • bits:具体类信息(成员变量、属性、方法)。

bitsclass_rw_t 类型的
我们通过源码得到

struct class_rw_t {
.....不关注.....
   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};
        }
    }

}
复制代码

封装了获取 methodspropertiesprotocols的方法。我能怎么获取呢?

2.2 类的内存组成

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //uintptr_t 8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;  // mask_t 4字节
#if __LP64__
            uint16_t                   _flags;  // 2 字节
#endif
            uint16_t                   _occupied; // 2 字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
复制代码

是由2部分组成 _bucketsAndMaybeMaskunion 联合体中有一个 _originalPreoptCache (指针 8字节)与结构体( _originalPreoptCache 与结构体只需要计算一个,共用一块内存)。所以cache_t大小为 16 字节。

2.3 lldb分析

2.3.1 isa 打印验证

截屏2021-06-19 下午4.29.33.png
通过打印可以验证之前isa指针指向

2.3.2 继承 打印验证

截屏2021-06-19 下午4.43.01.png
打印发现父类的指针地址确实指向父类,NSObject 的父类nil

2.3.3 元类 继承验证

截屏2021-06-19 下午5.03.32.png
通过打印类的内存地址找到元类,用元类的内存地址打印找到父元类,之后继续查找到nsobject ,nsobject的父类为空

2.3.4 属性 打印验证

截屏2021-06-18 下午5.14.37.png
通过控制台进行打印。

  1. 前面得到了想要获取 bites要进行首位置偏移8+8+16个单位
  2. 拿到后进行强转class_data_bits_t *
  3. 调用 data() 函数获取 class_rw_t指针
  4. 打印 class_rw_t

我们要获取属性
通过properties获取到的属性是一个property_array_t,结构如下:

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
复制代码

截屏2021-06-19 下午5.22.49.png

通过访问 ptr 能够获取到 property_list_t 数组:
$4 就相当于迭代器了,property_list_t源码结构如下:

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
复制代码

最终通过get方法获取属性但是没有 成员变量说明成员变量不存在这。

2.3.4 方法 打印验证

上面我们知道class_rw_t中有属性列表还有methods 同样的方式打印方法

截屏2021-06-19 下午5.38.33.png
一共8个方法,3个属性的setget方法 共6个,一个自定义的实例方法,一个 alloc 方法,没有 类方法(是method_t中有一个结构体big中有name和imp,并且提供了一个big方法,所以可以通过big方法获取)

struct method_t {
//......
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
//......
};
复制代码

2.3.4 成员变量 打印验证

properties中并没有找到成员变量,但是在class_rw_t的methods()附近发现了ro(),它返回class_ro_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;
};
复制代码

ivar_list_t类型:


struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};


复制代码

截屏2021-06-19 下午5.51.56.png
一共6个属性,完整的得到了

2.3.4 类方法 打印验证

在之前的methods()中并没有找到类方法,根据isa的走位直接进去元类查看。元类的结构也是objc_class所以可以尝试同样的方式获取。

截屏2021-06-19 下午5.59.17.png
都是objc_class所以可以用相同的方式进行获取,最终得到了类方法。
题外:实例对象包含成员变量的具体值

@implementation LGTeacher
- (instancetype)init{
   
    self = [super init];
    if (self) {
        self.nickName = @"父亲";
        [self saySomething];
        return self;
    }
    return nil;
}

复制代码

截屏2021-06-19 下午6.06.32.png

总结:

instance 对象(实例对象):成员变量的具体值、isa 指针
复制代码
class 对象(类对象):对象方法、属性、成员变量、协议信息、isa 指针、superclass 指针
复制代码
meta-class 对象(元类对象,即描述对象的对象,元数据是指描述数据的数据):类方法、isa 指针、superclass 指针。
复制代码

我通过源码分析和实际的lldb打印有了更深的理解,对于isa走位图理解更深刻了。

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