美文网首页
Runtime最新版

Runtime最新版

作者: Queen_BJ | 来源:发表于2020-09-14 15:31 被阅读0次

一、Runtime旧版定义

在OC1.0中,Runtime很多定义都写在NSObject.h文件中,如果之前研究过Runtime的同学可以见过下面的定义,定义了一些基础的信息。

// 声明Class和id
typedef struct objc_class *Class;
typedef struct objc_object *id;

// 声明常用变量
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;

// objc_object和objc_class
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class {  
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

旧版Runtime结构也比较简单,直接结构体定义,现在新版的Runtime在操作的时候,各种地址偏移操作和位运算。

二、Runtime新版的定义

可能苹果也不太想让开发者知道Runtime内部的实现,所以就把源码定义从NSObject中搬到Runtime中了。而且之前的定义也不用了,通过OBJC_TYPES_DEFINED预编译指令,将之前的代码废弃调了。
现在NSObject中的定义非常简单,直接就是一个Class类型的isa变量,其他信息都隐藏起来了。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

新的一些常用Runtime定义

typedef struct objc_class *Class;
typedef struct objc_object *id;

typedef struct method_t *Method;
typedef struct ivar_t *Ivar;
typedef struct category_t *Category;
typedef struct property_t *objc_property_t;

对象结构体(objc_object定义)

在OC中,每个对象都是一个结构体,结构体中都包含一个isa的成员变量,位于成员变量的第一位。isa的成员变量之前都是class类型的,后来改为isa_t

struct objc_object {
private:
    isa_t isa;
};

OC中的类和元类一样,都是结构体构成的,由于类的结构体定义继承objc_object,所以其也是个对象,并且具有对象的isa特征。



所以可以通过isa_t来查找对应的类或元类,查找方法通过** uintptr_t** 类型的bits,(uintptr_t存放指针地址。它们提供了一种可移植且安全的方法声明指针,而且和系统中使用的指针长度相同,对于把指针转化成整数形式来说很有用)通过按位操作来查找isa_t指向的类的地址。

实例对象或类对象的方法,并不会定义在各个对象中,而是定义在isa_t指向的类中,查找到对应的类后,通过类的class_data_bits_t类型的bits结构体查找方法,对象、类、元类都是同样查找原理。

isa_t定义

isa_t 是一个union的结构体,(union叫共用体,在一个“共用体”内能够定义多种不同的数据类型,这些数据共享同一段内存,以达到节省空间的目的)union类似于C++结构体,其内部可以定义成员变量和函数。在isa_t中定义了cls、bits、isa_t三部分,下面的struct结构体就是isa_t的结构体构成。

下面对isa_t中的结构体进行了位域声明,地址从nonpointer起到extra_rc结束,从低到高进行排列。位域也是对结构体内存布局进行了一个声明,通过下面的结构体成员变量可以直接操作某个地址。位域总共占8字节,所有的位域加在一起正好是64位。

小提示:union中bits可以操作整个内存区,而位域只能操作对应的位。

下面的代码是不完整代码,只保留了arm64部分,其他部分被忽略掉了。

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; // 是32位还是64位
        uintptr_t has_assoc         : 1; // 对象是否含有或曾经含有关联引用,如果没有关联引用,可以更快的释放对象
        uintptr_t has_cxx_dtor      : 1; // 表示是否有C++析构函数或OC的析构函数
        uintptr_t shiftcls          : 33; // 对象指向类的内存地址,也就是isa指向的地址
        uintptr_t magic             : 6; // 对象是否初始化完成
        uintptr_t weakly_referenced : 1; // 对象是否被弱引用或曾经被弱引用
        uintptr_t deallocating      : 1; // 对象是否被释放中
        uintptr_t has_sidetable_rc  : 1; // 对象引用计数太大,是否超出存储区域
        uintptr_t extra_rc          : 19; // 对象引用计数
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
// ····
# else
// ····
# endif
};

在arm64架构下,isa_t是以以下结构进行布局。在不同的CPU架构下,布局方式会有所不同,但参数都是一样的。


类结构体(objc_class结构体)

在Runtime中类也是一个对象,类的结构体objc_class是继承 objc_object,具备类的所有特征。
在objc_class中定义了三个成员变量,superclass是一个objc_class类型的指针,指向其父类的objc_class结构体。
cache用来处理已调用方法的缓存。
bits,只定义了一个uintptr_t类型的bits成员变量,存储了class_rw_t的地址。objc_class结构体中定义的一些函数,其内部都是通过bits实现的。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;    

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

从objc_class的源码可以看出,可以通过bits结构体的data()函数,获取class_rw_t指针。
我们进入源代码中看一下,可以看出是通过对uintptr_t类型的bits变量,做位运算查找对应的值。

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

uintptr_t本质上是一个unsigned long的typedef,unsigned long在64位处理器中占8字节,正好是64位二进制,通过FAST_DATA_MASK转换为二进制后,是取bits中的47-3的位置,正好是取出class_rw_t指针。

在OC中一个指针的长度是47,例如打印一个UIViewController的地址是0x7faf1b580450,转换为二进制是11111111010111100011011010110000000010001010000,后面三位是占位的,所以在取地址的时候会忽略最后三位。

// 查找第0位,表示是否swift
#define FAST_IS_SWIFT           (1UL<<0)
// 当前类或父类是否定义了retain、release等方法
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// 类或父类需要初始化isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// 数据段的指针
#define FAST_DATA_MASK          0x00007ffffffffff8UL
// 11111111111111111111111111111111111111111111000 总共47位

因为在bits中最后三位是没用的,可以用来存储一些其他信息。在class_data_bits_t还定义了三个宏,用来对后三位做位运算。

class_ro_t和class_rw_t

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

在编译后class_data_bits_t指向的是一个class_ro_t的地址,这个结构体是不可变的(只读),在运行时,才会通过realizeClass函数将bits指向class_rw_t。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

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

在程序开始运行后会初始化Class,在这个过程中,会把编译器存储在bits中的class_ro_t取出,然后创建class_rw_t,并把ro赋值给rw,成为rw的一个成员变量,最后把rw设置给bits,替代之前bits中存储的ro。

下面是初始化Class的精简版代码。
static Class realizeClass(Class cls) 
{
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;

    ro = (const class_ro_t *)cls->data();
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);

    isMeta = ro->flags & RO_META;
    rw->version = isMeta ? 7 : 0;

    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()))

    cls->superclass = supercls;
    cls->initClassIsa(metacls);
    cls->setInstanceSize(ro->instanceSize);

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls);
    return cls;
}

在上面的代码中我们还发现了两个函数,addRootClass和addSubclass函数,这两个函数的作用是将某个类的子类串成一个列表,由此可知,我们是可以通过class_rw_t,获取到当前类的所有子类。

superClass.firstSubclass -> subClass1.nextSiblingClass -> subClass2.nextSiblingClass -> ...

初始化rw和ro之后,rw的method list、protocol list、property list都是空的,需要在下面methodizeClass函数中进行赋值。函数中会把ro的list都取出来,然后赋值给rw,如果在运行时动态修改,也是对rw做的操作
所以ro中存储的是编译时就已经决定的原数据,rw才是运行时动态修改的数据

static void methodizeClass(Class cls)
{
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
}

假设创建一个类LXZObject,继承自NSObject,并为其加入一个testMethod方法,不做其他操作。因为在编译后objc_class的bits对应的是class_ro_t结构体,所以我们打印一下结构体的成员变量,看一下编译后的class_ro_t是什么样的。

struct class_ro_t {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f7a "LXZObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
}

过打印可以看出,一个类的class_ro_t中只会包含当前类的信息,不会包含其父类的信息。在LXZObject类中只会包含name和baseMethodList两个字段,而baseMethodList中只有一个testMethod方法。由此可知,class_rw_t结构体也是一样的。

初始化过程

下面是已经初始化后的isa_t结构体的布局,以及各个结构体成员在结构体中的位置。


union经常配合结构体使用,第一次使用union就是对结构体区域做初始化。在对象初始化时,会对isa_t的bits字段赋值为ISA_MAGIC_VALUE,这就是对union联合体初始化的过程

Tagged Pointer(标记指针)Tagged Pointer

从iPhone5s开始,iOS设备开始引入了64位处理器,之前的处理器一直都是32位的。
是在64位处理器中,指针长度以及一些变量所占内存都发生了改变,32位一个指针占用4字节,但64位一个指针占用8字节;32位一个long占用4字节,64位一个long占用8字节等,所以在64位上内存占用会多出很多。

苹果为了优化这个问题,推出了Tagged Pointer新特性。之前一个指针指向一个地址,而Tagged Pointer中一个指针就代表一个值,以NSNumber为例。

NSNumber *number1 = @1;
NSNumber *number2 = @3;
NSNumber *number3 = @54;

// 输出
(lldb) p number1
(__NSCFNumber *) $3 = 0xb000000000000012 (int)1
(lldb) p number2
(__NSCFNumber *) $4 = 0xb000000000000032 (int)3
(lldb) p number3
(__NSCFNumber *) $5 = 0xb000000000000362 (int)54

通过上面代码可以看出,使用了Tagged Pointer新特性后,指针中就存储着对象的值。例如一个值为1的NSNumber,指针就是0xb000000000000012,如果抛去前面的0xb和后面的2,中间正好就是16进制的值

苹果通过Tagged Pointer的特性,明显的提升了执行效率并节省了很多内存。在64位处理器下,内存占用减少了将近一半,执行效率也大大提升。由于通过指针来直接表示数值,所以没有了malloc和free的过程,对象的创建和销毁速度提升几十倍。

method_t

函数四要素:名称,返回值,参数,函数体

struct method_t {
  SEL name;           //名称
  const char *types;//返回值和参数
  IMP imp;              //函数体
}

cache_t

用于快速查找方法执行函数,是可增量扩展的哈希表结构,是局部性原理的最佳运用

 struct cache_t {
    struct bucket_t *_buckets;//一个散列表,用来方法缓存,bucket_t类型,包含key以及方法实现IMP
    mask_t _mask;//分配用来缓存bucket的总数
    mask_t _occupied;//表明目前实际占用的缓存bucket的个数
}
struct bucket_t {
    private:
    cache_key_t _key;
    IMP _imp;
 }

参考代码

相关文章

  • Runtime最新版

    一、Runtime旧版定义 在OC1.0中,Runtime很多定义都写在NSObject.h文件中,如果之前研究过...

  • Runtime objc4-750.1编译

    最新版本的Runtime源码编译环境配置(写的不好,见谅) 当前环境 mac OS 10.14 Xcode 10....

  • iOS 类的结构解析

    一、源码静态解读 1、在objc最新版本objc-781,可以在objc-runtime-new.h找到objc_...

  • iOS之RunTime探索与实践

    Runtime 概念 Runtime 相关概念 Runtime 实践 Runtime概念 Runtime简称运行时...

  • OC -> Runtime

    Runtime简介 Runtime用处 Runtime实践 Runtime 类方法调用实现。Person * p ...

  • iOS runtime(三)runtime之method(1)m

    iOS runtime(一)runtime之Property 详尽iOS runtime(二)runtime之Iv...

  • java中调用python的几种方式(记录)

    第一种:通过runtime Runtime runtime = Runtime.getRuntime(); try...

  • 自己实现OC的KVO

    Runtime系列文章在这:Runtime介绍---术语介绍Runtime应用--动态添加方法Runtime应用-...

  • 最新Runtime源码objc4-750编译

    最新版本的Runtime源码已经出来了,是不急不可耐的想用用它呢?在这里我将一步步教大家如何编译它,首先贴个自己的...

  • objc4-750编译

    最新版本的Runtime源码已经出来了,是不急不可耐的想用用它呢?在这里我将一步步教大家如何编译它,首先贴个自己的...

网友评论

      本文标题:Runtime最新版

      本文链接:https://www.haomeiwen.com/subject/bnmaghtx.html