Runtime从入门到放弃(1)- 了解isa和Class

326 阅读5分钟

一.Runtime介绍?

OC是一门动态性语言,允许很多操作从‘编译阶段’、‘链接阶段’推迟到’运行时阶段‘,实现该操作的基础就是Runtime。 静态语言是在编译阶段就已经确定了所有变量的数据类型和函数的实现,C,C++,Java都是静态语言。 动态语言是在程序运行时可以改变数据类型及结构,到运行时才去确定一个对象的类型以及调用该类型对象的方法。 Runtime是一套封装了C/C++/汇编语言的API,内部包含很多动态性相关的函数

二.isaClass

1.什么是isa

arm64架构之前,isa就是个普通的指针,存储类对象和元类对象的内存地址 arm64架构之后,isa变成了一个联合体结构(union),并且使用了位域来存储更多的信息

isa_t是一个联合体(联合体中的变量共用同一块地址空间。也就是说,cls、bits、结构体共用同一块地址空间)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    // 相当于是unsigned long bits;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
// 这里的定义在isa.h中,如下(注意uintptr_t实际上就是unsigned long)
//    uintptr_t nonpointer        : 1;
//    uintptr_t has_assoc         : 1;
//    uintptr_t has_cxx_dtor      : 1; 
//    uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
//    uintptr_t magic             : 6;
//    uintptr_t weakly_referenced : 1;
//    uintptr_t deallocating      : 1;
//    uintptr_t has_sidetable_rc  : 1;
//    uintptr_t extra_rc          : 8
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

struct objc_object {
    // isa结构体
private:
    isa_t isa;

public:
    // 类指针
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
...
}

2.类的结构(Class

2-1.objc_class结构

Class实际上是一个指向objc_class结构体的指针

// Objective-C中的类其实是objc_class结构体
typedef struct objc_class *Class;
// 这里就是Objective-C中用到的id类型
typedef struct objc_object *id;

objc_class(类对象结构体)继承objc_object(实例对象结构体)

// objc_class继承于objc_object,因此
// objc_class中也有isa结构体
struct objc_class : objc_object {
    // ISA占8位
    Class ISA;
    // superclass占8位
    Class superclass;
    // 缓存的是指针和vtable,目的是加速方法的调用  cache占16位
    cache_t cache;             // formerly cache pointer and vtable
    // class_data_bits_t 相当于是class_rw_t 指针加上rr/alloc标志
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() {
        // 这里的bits就是class_data_bits_t bits;
        return bits.data();
    }
...
}

2-1-1.Class ISA

除了实例对象有isa指针,类对象也有isa指针,关联着它的元类 Class本身是一个指针,占用8个字节

2-1-2.Class superclass

类的父类(NSObject),Class类型,占用8个字节

2-1-3.cache_t cache

cache_t就是方法缓存,是一个哈希表,已调用过的方法会换存在里面,后续重新调用时能提高查询速度。

存取原理

  • 存放:如果生成的索引在 buckets 下已经存在 data 。那么他会把 index - 1,减到零了还没有空闲位置,它会从数组最大值开始继续往前找位置,直到有位置;
  • 获取:在拿到 bucket_t 后,会比较一下 key@selector(methodName) 是否对应,如果不对应,那就回按照存放的那样方式一个一个找。如果存满了,buckets 就会走扩容。
struct cache_t {
    // 哈希表,缓存已调用的方法,是一个指针
    struct bucket_t *buckets() const; 
    // 哈希表长度 int类型
    mask_t mask(); 
    // 已缓存的方法数量 占4个字节
    mask_t occupied() const;
}
struct bucket_t {
    // 方法选择器
    inline SEL sel() const { 
        return _sel.load(memory_order_relaxed); 
    }
    // 函数内存地址,函数实际调用的入口
    inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const
    {
        uintptr_t imp = _imp.load(memory_order_relaxed);
        if (!imp) return nil;
        imp ^= (uintptr_t)cls;
        return (IMP)imp;
    }
    
}

2-2.class_data_bits_t结构

class_data_bits_t用来存储数据,friend objc_class表明它有元类,uintptr_t bits通过掩码的形式保存class_rw_t是否是swift类等一些标志位

bool getBit(uintptr_t bit) const解析:

  • const 表示该方法内部不会修改 class_data_bits_t 的内部数据
  • 返回值是一个 bool 类型,通过与操作来取出 bits 的指定位的值来进行判断

class_rw_t* data() const解析:

  • bits 中读出 class_rw_t 指针 void setData(class_rw_t *newData)解析:
  • 设置class_rw_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
    {
    /* 
    内部实现只有一个与操作,主要根据入参 bit(掩码) 来取得一些标识位 
    如: 
    
    #define FAST_IS_SWIFT_LEGACY (1UL<<0) 
    使用 bit 的第 0 位来进行判断 
    
    #define FAST_IS_SWIFT_STABLE (1UL<<1)
    bit 的 第 1 位 判断类是否是稳定的 Swift ABI 的 Swift 类 
    */
        return bits & bit;
    }
public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData) {
        ...
    }
}

2-3.class_rw_t结构

// 类的方法、属性、协议等信息都保存在class_rw_t结构体中
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    // 方法信息
    method_array_t methods;
    // 属性信息
    property_array_t properties;
    // 协议信息
    protocol_array_t protocols;
...
}
  • class_rw_t 里面的 methodspropertiesprotocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容.
  • 类似于这样:
methods = @[@[method_t,method_t,method_t],
            @[method_t,method_t,method_t],
            @[method_t,method_t,method_t]]

2-4.class_ro_t结构

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    // 方法列表
    method_list_t * baseMethodList;
    // 协议列表
    protocol_list_t * baseProtocols;
    // 变量列表
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    // 属性列表
    property_list_t *baseProperties;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
  • class_ro_t结构体存储了类在编译期就已经确定的属性、方法以及遵循的协议。因为在编译期就已经确定了,所以是ro(readonly)的,不可修改。
  • baseMethodListbaseProtocolsivarsbaseProperties 是一维数组,是只读的,包含了类的初始内容。

2-5.method_t结构

method_t包含了方法的名称(SEL)、方法的实现(imp,函数的指针)

struct method_t {
    // 方法、函数名
    SEL name;
    // 编码 (返回值类型、参数类型)
    const char *types;
    // 指向方法、函数的指针(函数地址)
    MethodListIMP imp;
    struct SortBySELAddress :
        public std::binary_function<const method_t&, 
                                    const method_t&, bool> {
        bool operator (const method_t& lhs,
                       const method_t& rhs) { 
                       return lhs.name < rhs.name;
        }
    };
};

2-5-1.SEL是什么?

  • SEL中文名叫选择器,表示方法名,是方法在Runtime期间的标识符。
  • 代码定义:
// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

虽然SELobjc_selector结构体指针,实际上只是个C字符串;在类加载的时候,编译器会生成与方法相对应的选择器,并注册到Runtime运行时系统中;无论两个类是否是否存在依存关系,只要它们拥有相同的方法名,那它们的SEL都是相同的。(所有controllerviewDidloadSEL都是同一个)

2-5-2.imp是什么?

imp代表函数指针,即函数执行的入口 代码定义:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ )#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
  • 第一个id类型的参数代表当前实例的地址,如果是类则指向它的元类,作为消息的接收者
  • 第二个SEL类型的参数代表选择器,也就是方法名
  • 第三个参数...代表可选参数
  • 返回值是个id类型(OBJC_OLD_DISPATCH_PROTOTYPES表示旧版,目前都是用新版)

2-5-3.types是什么?

types 包含了函数返回值、参数编码的字符串。通过字符串拼接的方式将返回值和参数拼接成一个字符串,来代表函数返回值及参数。

3.分类结构体(category_t

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods; // 对象方法
    struct method_list_t *classMethods; // 类方法
    struct protocol_list_t *protocols; // 协议
    struct property_list_t *instanceProperties; // 属性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};