Objective-C 分类底层原理分析

1、什么是分类

iOS分类使用的场景就是把功能模块化,把一组类似的功能扩展出一个分类,便于代码的维护以及功能的分块

2、分类的底层结构

分类在iOS底层的数据结构如下:

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

name:被分类的类名称
cls :isa指针
instance_methods:对象方法列表
class_methods:类方法列表
protocols:协议列表
properties:属性列表

有上面的数据结构可以看出:

分类可以扩展一个类的(对象方法,类方法,协议和属性)。

name:被分类的类名称

cls :isa指针

instance_methods:对象方法列表

class_methods:类方法列表

protocols:协议列表

properties:属性列表

有上面的数据结构可以看出:

分类可以扩展一个类的(对象方法,类方法,协议和属性)但不能增加成员变量。同时,添加的属性不会生成get和set方法的实现,如果需要,则要用运行时动态添加

其实我们写的所有分类的数据结构都是跟上面一样的,不同的是里面存放的数据 

3、实践证明分类的结构体

例如我创建一个Person+Eat的分类

#import "Person.h"
NS_ASSUME_NONNULL_BEGIN

@interface Person (Eat)
-(void)eat;
@end

NS_ASSUME_NONNULL_END
#import "Person+Eat.h"

@implementation Person (Eat)
-(void)eat {
    NSLog(@"eat-----");
}
@end

运行之后,用命令行(clang -rewrite-objc Person+Eat.m -o Person+Eat.cpp)生成c++代码。产生的C++相关代码如下:

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"Person",
	0, // &OBJC_CLASS_$_Person,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat,
	0,
	0,
	0,
};

上面可以看出:这个分类的类型就是上面第二步说到的《  _category_t 》 类型,结构体的赋值如下:

name:"Person"
cls : &OBJC_CLASS_$_Person
instance_methods:Eat方法
class_methods:没有,所以为空
protocols:没有,所以为空
properties:没有,所以为空

4、分类的实现原理

上面几个步骤说到的只是分类的结构,是程序编译的时候就已经生成的。 但这个分类跟对应的类关联在一起是在运行时,这个就用到oc的runtime机制了,runtime在程序运行的过程中,会把所有的分类,合并到对应的类或者原类里面去,如果有同名方法,会优先调用分类里面的方法(利用这个功能,我们可以对系统的类做方法交换)

分类的实现的具体步骤:

所有分类的方法会存放在一个二维数组里面,二维数组的每一个数组就是其中一个分类的所有方法,在运行时,会遍历这个数组,然后把所有的方法添加到对应的类里面去,具体添加的步骤如下

1、根据要添加的方法数组大小加上原来数组的大小,重新分配数组空间,

2、先把原来类的数组的存储地址向后面移动n个单元,n取决于二维数组的大小,然后把新传进来的数组从大到小的顺序进行遍历,一个一个插入到新分配的数组空间里面去,因为从大到小的顺序进行遍历,也就是数组后面的会排在第一个。二维数组的顺序是编译的先后顺序决定的,所以同一个方法名,后编译的分类的方法比先编译的方法优先执行

把分类里面的方法合并到对应的类中的核心源码:

/**
      * addedLists 所有分类的方法列表
      * addedCount 有多少个分类
      */
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            // 拿到旧的数组空间
            uint32_t oldCount = array()->count;
            // 计算新的数组空间
            uint32_t newCount = oldCount + addedCount;
            // 重新分配内存
            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]));
        }
    }

右上图可以看出为什么分类的方法和原来类的方法同名的情况下,会优先调用分类方法,因为分类方法在数组的前面,最先被找到。

 

5、这里顺便提一下类扩展

类扩展是程序编译的时候就确定了的,类扩展一般是扩展私有属性和方法

发布了79 篇原创文章 · 获赞 42 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/s12117719679/article/details/103157316