IOS开发知识点

IOS底层(九): 类相关: 类结构分析

2021-04-14  本文已影响0人  ShawnAlex

OC底层源码/原理合集

建议先看下
IOS底层(三): alloc相关1.初探 alloc, init, new源码分析
IOS底层(八): alloc相关: isa与类关联源码分析

首先看个例子:

exp

XXX & 0x00007ffffffffff8ULL这块先解释下, 之前我们讲过ISA源码

#   define ISA_MASK        0x00007ffffffffff8ULL  // x86_64下

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

这里 isa.bits & ISA_MASK 是返回类信息

我们分解看下上面例子

其实第二次时候就应该有疑问, 为什么还是SATest, 而第三次是NSObject

第二次中 0x0000000100008360是之前isa中获取的isa的指针地址, 即SATest类的类, 在Apple中我们称SATest类的类元类, 之后的NSObject也称为元类, 不过由于它源自根类, 所以也可以成为根元类

(下面图片是新走一遍的结果, 之前不小心给关了, 打印地址可能有点差别, 原理不变)


元类探索

元类

  1. 首先了解一点, 对象isa指向的, 类也是一个对象( 所以有个流传, 万物皆对象:) ), 这个对象我们一般称为类对象, 其isa位域指向苹果定义的元类

  2. 元类系统给的, 其定义创建都是由编译器完成, 归属来自于元类

3.元类类对象, 每个都有一个独一无二的元类用来存储类方法相关信息

4.元类本身是没有名称的, 由于与关联, 所以使用了同类名一样的名称

由上面例子也可以得到关系
对象元类NSObject, NSObject元类指向自身

总结
元类走位图

扩展个问题, 刚才看到自定义的一个类, 有元类有根元类依次循环, 那么岂不是, 创建一个类, 系统会自动帮我们创建多个NSObject?

我们可以这样验证下

        Class cls1 = [SATest class];
        Class cls2 = [SATest alloc].class;
        Class cls3 = object_getClass([SATest alloc]);
        NSLog(@"cls1: %p", cls1);
        NSLog(@"cls2: %p", cls2);
        NSLog(@"cls3: %p", cls3);
验证类1

可看到打印地址只有一个, 所以NSObject只有一份, 或者说NSObject(根元类)在内存中只有一份。(类的信息在内存中永远只存在一份, 类对象只有一份)

或者通过lldb验证也可以, 看下下面图片即可(根元类只是指向自己)

验证类2

isa走势关系图

isa走势关系图

(留意下虚线是isa, 实线是superclass)

实例对象(Instance) isa指向 → Class(类) isa 指向 → meta(元类) isa 指向 → root (meta 根元类), 而root (meta 根元类) isa 指向 → NSObject

父类实例对象也是一样走势

自定义类打印 NSObject打印

如果是类NSObject, 会省去一个元类指向

子类(SubClass) 继承 → 父类(SuperClass) →继承 → 根类(Root Class)
根类即NSObject无继承

子元类(meta SubClass) 继承 → 父元类(meta SuperClass) →继承 → 根元类(meta Root Class) 继承 → NSObject

isa走势关系图标注

objc_class & objc_object

既然提到了类和对象, 我们看下objc_classobjc_object源码


1. typedef struct objc_class *Class;

2. 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 *` */

// objc-runtime-new.h
3. 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)
// objc.h

1. struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

// objc-private.h

2. 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;

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);
...

首先可以看到objc_classobjc_object都有一个 isa 指针, 这个isa来自于objc_object(struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };), 且在新版objc-runtime-new.h里面可看到objc_class是继承于objc_object

总结:

objc_class、objc_object、isa、object、NSObject关系图

类结构分析

首先还是先看一个例子

例子1: 普通指针
普通指针

定义2个变量a, b = 10, 打印两个变量值以及内存地址

普通指针地址指向
例子2: 对象指针
对象指针 对象指针地址指向
例子3: 数组指针
数组指针

有了上面的概念, 便于我们理解之后的探索


bits探索

再回头看下objc_class源码(在 objc-runtime-new.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_rw_t *data() const {
        return bits.data();
    }

...
}

抛去那些删除的, ISA8字节, superclassClass类型占8字节(这里的Class是由objc_object定义的,是一个指针), bits 只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits(bits有所以我们想要的信息), 那么我们接下来看下cache

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

先介绍几个定义

拆开看一下

占8字节
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // long类型占8字节

占8字节
        struct {
            explicit_atomic<mask_t>    _maybeMask; // int 4字节
#if __LP64__
            uint16_t                   _flags; // short 占2字节
#endif
            uint16_t                   _occupied; // short 占2字节
        };

接下来看下 originalPreoptCache

看下 preopt_cache_t
explicit_atomic<preopt_cache_t *> _originalPreoptCache;

/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_entry_t {
    uint32_t sel_offs;
    uint32_t imp_offs;
};

/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_t {
    int32_t  fallback_class_offset;
    union {
        struct {
            uint16_t shift       :  5;
            uint16_t mask        : 11;
        };
        uint16_t hash_params;
    };
    uint16_t occupied    : 14;
    uint16_t has_inlines :  1;
    uint16_t bit_one     :  1;
    preopt_cache_entry_t entries[];

    inline int capacity() const {
        return mask + 1;
    }
};

最后可计算出cache类的内存大小16字节, 那么总共需要偏移 8 + 8 + 16 = 32个字节。即首地址平移32位得到 bits(bit主要储存相关类信息)

创建2个类, SAPerson继承NSObject, SAStudent继承SAPerson

查看bit信息
 class_rw_t *data() const {
        return bits.data();
}

由于bits里面数据类型是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};
        }
    }

    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源码可看到, 首先class_rw_t也是个结构体类型, 结构体中有提供相应的方法去获取 properties 属性列表method 方法列表protocols协议列表

那么我们在SAPerson定义几个属性, 方法

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SAPerson : NSObject {
    NSString *hobby;
}

@property (nonatomic, copy) NSString *sa_name;
@property (nonatomic, assign) NSInteger sa_age;

- (void)sayYes;
+ (void)sayNo;


@end
属性列表

bits 数据信息 $3在之前的例子我上面已经讲过了, 我们从读bits数据信息$3之后开始

我们接下来看下方法


方法列表

当然你要读协议列表的list结构, 那里就p $3.protocols()即可

其实到这里我们会有疑问, 属性, 方法``协议打印出来了没问题, 但是成员变量hobby类方法sayNo并没有打印出来。我们再返回看一下struct class_rw_t , 里面除了methodspropertiesprotocols,还有一个ro方法 constclass_ro_t *ro(), 看下ro`底层

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;

  ...
};

可看到const ivar_list_t * ivars;, 有一个ivars属性, 我们仿照下上面也读一下ro

ro

可看到成员变量hoppy储存在ivar_list_t里面

总结

接下来我们看下类方法存在哪里


类方法

总结

上一篇下一篇

猜你喜欢

热点阅读