Runtime——探索类,对象,分类本质

Runtime是什么?

Runtime顾名思义为运行时,他其实是一套底层纯C语言的API,OC的代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言的基础,他使OC具备了面向对象性和动态性。

所谓动态性是基于c/c++这样的静态语言区别的,在c/c++中我们编译成什么就会执行什么,且不实现定义时会报错,而OC则不会,我们甚至可以使用方法调换技术,改变我们编译时执行的函数A,令其执行函数B。

那么我们之前写的OC代码是怎么和Runtime交互的,大概分为三种层次,OC源代码,NSObject方法,Runtime函数,交互程度是由高到低的。
下面探索一下OC对象的实质,他底层是怎么样的c语言代码。

类和对象本质

这里的话需要下载一下runtime源码——Runtime
我们找到源码中关于类和对象的定义可以看到。
在这里插入图片描述
类和对象的本质都是结构体。

对象本质

我们点进去对象这个结构体。

struct objc_object {
    
    
private:
    
    // 私有成员变量: isa指针
    isa_t isa;
    // 自定义的一些成员变量
public:
// 一些共有方法.
}

可以看到这块一个系统自带的变量isa指针,点进去发现他是个共用体。

// MARK: - isa的声明
// 这里分析一下共用体
/* 共用体和结构体其实都可以定义很多变量,区别就是共用体占用的内存为变量最宽的内存。
    共同体中所有的成员变量共同使用这一块内存,很节省空间,但容易覆盖
*/
union isa_t {
    
    
    /// isa_t的构造函数
    isa_t() {
    
     }
    isa_t(uintptr_t value) : bits(value) {
    
     }

    Class cls;
    uintptr_t bits; // 八个字节 64位
#if defined(ISA_BITFIELD)
    struct {
    
    
        // isa 结构的定义
        /* 其实所有的数据都存储在成员变量bits里面,这个结构体利用位域
         来增加代码可读性,让我们看看bits对应的位上分别存储什么。
         */
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;          /* isa是否经过优化,如果指为0,代表无优化,就是一个isa指针,
        64位全部用来存储对象所属类的地址,如果为1,则采取当前位分配。*/                             \
      uintptr_t has_assoc         : 1;      /*判断有无关联对象,如果没有快速销毁对象。 */                                 \
      uintptr_t has_cxx_dtor      : 1;      /*判断当前对象有无析构函数,更快销毁 */                                 \
      uintptr_t shiftcls          : 33; /*当前对象所属类的地址信息 */ \
      uintptr_t magic             : 6;     /*标记调试时,当前对象是否初始化 */                                  \
      uintptr_t weakly_referenced : 1;  /*标记弱引用表里是否有当前对象弱指针数组,也就是是否被若指针指向,以及是否有弱引用*/                                    \
      uintptr_t deallocating      : 1;         /*当前对象是否释放。 */                              \
      uintptr_t has_sidetable_rc  : 1;  /*引用计数表里面是否有当前对象的引用记数*/                                      \
      uintptr_t extra_rc          : 19/*对象的引用计数 - 1,存不下了就回放到引用计数表中*/
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      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
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT

可见对象的本质是一个objc_object结构体,该结构体的内部有一个固定的成员变量isa,在64位操作系统以后对isa做了优化,它不再是一个指针,而是一个isa_t的共用体,其占用8个字节64位,在不同架构(arm64,x86)下各位段长度不一样,在arm64中,33位被用来存储对象所属类的地址信息,19位存储对象的引用计数-1这一操作,存不下的放到引用计数表里(存值范围为0-255),1位表示是否有若引用,其他位也有各种标记信息。

类的本质

struct objc_class : objc_object {
    
    
    // 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,内部存储方法,属性,遵循的协议等
}
// MARK: - class_rw_t结构声明
struct class_rw_t {
    
    
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    // class_ro_t成员变量 ,只读性
    const class_ro_t *ro;

    method_array_t methods;//存储着该类的所有实例方法,包括分类
    property_array_t properties;//存储着该类的所有属性,包括分类
    protocol_array_t protocols;// 存储着该类所有遵守的协议信息,包括分类

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}
// MARK: - class_ro_t结构声明
// 该类的只读信息ro-readonly
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;
    }
};

可以看出类的本质还是一个 objc_class类型结构体,只不过其内部结构套了两层rwro
class_ro_t:只读信息,内部存储着经过编译后一个类本身定义的所有实例方法,属性,协议,成员变量。
class_rw_t是在运行时系统生成的,把ro里本身定义的信息复制到rw里,并把分类相关的信息合并到rw中,rw是可读可写的,这就解释了分类为什么不能给类增加成员变量。

元类本质

所谓元类,是指一个类所属的类,我们每创建一个类,系统就会自动帮我们创建好该类所属的类——即元类。因此在OC里对象其实分为实例对象、类对象、元类对象三类,我们开发中经常说的“对象”其实是指狭义的对象一实例对象,知道了这一点就好理解了,实例对象有它所属的类-即一个类对象,类对象也有它所属的类–即一个元类对象,元类对象也有它所属的类–即基类的元类对象。

其实元类和类的本质都是objc_class结构体,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法信息,而元类的methods成员变量里存储着该类所有的类方法信息。

所有元类都使用根元类作为他们的类,根元类的isa指针指向了它自己。

扫描二维码关注公众号,回复: 14691348 查看本文章

三者的关系。
使用最常见的这张图进行分析 instance(实例) Subclass(子类)

每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类)。
在这里插入图片描述

分类的本质

分类是OC的一个高级特性,我们一般用它给系统的类或者第三方库的类扩展方法,属性和协议,或者把一个类不同功能分散到不同的模块中实现。
在这里插入图片描述
在runtime中查看一下分类的源码。

// MARK: - category 完整声明结构
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);
};

就是一个结构体,然后可以看到里面是没有成员变量列表存在的,这样可以解释分类为什么不能添加成员变量。

分类的实现原理

通过上面我们可以分析得到,我们知道一个类的实例方法存储在类里面,所有的类方法在元类里面,而对象调用方法isa指针先到相应的类的和元类,然后在其方法列表中查找,那么分类是这么实现这一功能的。

因为isa指针指向了分类的所属类,所以肯定不是这一套,正确的应该是编译器在运行时利用Runtime动态把分类的数据合并到类和元类,分类的数据在类本身数据之前,越晚编译的分类越在前面,所以如果分类里面有和类里面同名的方法,就会优先调用分类里的方法,如果多个分类有同名的方法,会优先调用后编译方法里面的方法,我们可以去Compile里控制分类编译顺序。

那么Runtime具体是怎么实现的?
运行时,系统读取镜像阶段,会读取所有的类,如果发现分类,遍历所有的分类,根据分类的cls指针找到它所属的类,重新组织一下这个类内部的结构——即合并分类的数据。
以下是runtime.new中的read_images方法,这个方法是处理镜像,这里取出了关于分类的部分。

// Discover categories. 
    for (EACH_HEADER) {
    
    
        // 读取当前类所有的分类
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        // 倒序取出每一个分类
        // 越晚编译的分类的实例方法列表反而在最前面
        for (i = 0; i < count; i++) {
    
    
            // 读取某一个分类
            category_t *cat = catlist[i];
            // 根据这个分类的cls指针找个这个分类所属的类
            Class cls = remapClass(cat->cls);
            // 处理cls指针缺失的特殊情况
            if (!cls) {
    
    
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
    
    
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
        // 开始合并分类的数据
            // 先将实例方法,协议,属性添加到基类里面
            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
    
    
                // 取出当前分类(没有合并过的),合并实例方法,以及协议。
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
    
    
                    // 合并到所属类的方法列表里面
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
    
    
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            // 然后将拓展的类方法,类协议,类属性添加到基类里面
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
    
    
                // 获取未合并的分类的信息,这里ISA指的所属类的元类
                // 与上文提到的类方法存储在元类中符合
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
    
    
                    // 合并到元类中
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
    
    
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

这里有两个关键的函数。
1.addUnattachedCategoryForClass,这个函数就是对比当前分类是否被合并

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    
    
    runtimeLock.assertLocked();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
    
    
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
    
    
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){
    
    cat, catHeader};
    NXMapInsert(cats, cls, list);
}


2.remethodizeClass,这个方法就是合并数据的方法。

// 将未处理的分类数据添加到现在的类,修复所属类的方法列表,协议列表,属性列表。
// 更新类和其子类的方法缓存
static void remethodizeClass(Class cls)
{
    
    
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
    
    
        if (PrintConnecting) {
    
    
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

/将方法列表、属性和协议从类别附加到类。//假设cat的分类都是加载并按加载顺序排序的,//最老的分类优先。
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    
    
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();
    // 开辟二维数组存放每个分类里面的某个列表。
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    // 可以看到这里时倒序遍历所有分类。
    while (i--) {
    
    
        // 获取一个分类
        auto& entry = cats->list[i];
        // 获取分类的类方法,存入二维数组
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
    
    
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 属性列表存入二维数组
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
    
    
            proplists[propcount++] = proplist;
        }
        // 协议列表存入二维数组
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
    
    
            protolists[protocount++] = protolist;
        }
    }
    // 将这个二维数组内所有一维数组的收地址存入rw类的methods成员变量指向的地址
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // 给当前类的实例方法列表附加所有分类的实例方法列表
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    // 存入属性
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    // 存入协议
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

// 添加方法列表具体实现
    // addedLists 所有分类的方法列表,就是二维数组
    // addedCount 分类个数
    void attachLists(List* const * addedLists, uint32_t addedCount) {
    
    
        if (addedCount == 0) return;

        if (hasArray()) {
    
    
            // many lists -> many lists
            // 获取原来的methods元素个数(元素为数组)
            uint32_t oldCount = array()->count;
            // 计算新的
            uint32_t newCount = oldCount + addedCount;
            // 重新为methods成员变量指向的数组分配内存,一个指针八个字节。
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 分配完内存后对内村数据进行移动和复制
            // 先将旧的数据移动到新开辟的空间的最后面,可以内存覆盖
            // 其实就是原来列表的首地址放在后面
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // 再将新列表的新数据复制到新开辟的空间最前面,没有内存覆盖
            // 新列表的首地址复制到前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
    
    
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
    
    
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

这样就完成了将所有分类的数据合并到类的操作。最终类的方法列表,分类的在前面,而原来的方法列表在后面。

猜你喜欢

转载自blog.csdn.net/chabuduoxs/article/details/125366193