Cx330

「重学iOS」Objective-C中Class的具体结构

2020-12-18 · 4 min read
runtime Objective-C iOS

我们之前在讲分类的时候讲到了类的大体结构,如下图所示:

01.jpg

就如我们之前讲到的,当我们调用方法的时候是从bits中的methods中查找方法,分类的方法是排在主类方法前面的,所以调用同名方法是先调用分类的,而且究竟调用哪个分类的方法要取决于编译的先后顺序等等:

02.png

那么这个rw_t中的methods和ro_t中的methods有什么不一样呢?

首先,ro_t中methods,是只包含原始类的方法,不包括分类的,而rw_t中的methods即包含原始类的也包含分类的;

其次,ro_t中的methods只能读取不能修改,而rw_t中的methods既可以读取也可以修改,所以我们今后在动态添加方法修改方法的时候是在rw_t中的methods去操作的;

然后,ro_t中的methods是个一维数组,里面存放着method_t(对方法/函数的封装,即一个method_t代表一个方法或函数),而rw_t中的methods是个二维数组,里面存放着各个分类和原始类的数组,分类和原始类的数组中存放着method_t。即:

03.png

我们也可以在源码中找到rw_t和ro_t的关系,

static Class realizeClass(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));
    
    // 最开始cls->data是指向ro的
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // rw已经初始化并且分配内存空间
        rw = cls->data(); // cls->data指向rw
        ro = cls->data()->ro; // cls->data()->ro指向ro  即rw中的ro指向ro
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 如果rw并不存在,则为rw分配空间
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);// 分配空间
        rw->ro = ro;// rw->ro重新指向ro
        rw->flags = RW_REALIZED|RW_REALIZING;
        // 将rw传入setData函数,等于cls->data()重新指向rw
        cls->setData(rw);
    }
}

首先,cls->data(即bits)是指向存储类初始化信息的ro_t的,然后在运行过程中创建了class_rw_t,等rw_t分配好内存空间后,开始将cls->data指向了rw_t并将rw_t中的ro指向了存储初始化信息的ro_t。

那么ro_t和rw_t中存储的这个method_t是个什么结构呢?我们阅读源码发现结构如下,我们发现有三个成员:name、types、imp,我们一一来看:

04.png
  • name,表示方法的名称,一般叫做选择器,可以通过@selector()和sel_registerName()获得。

比如test方法,它的SEL就是@selector(test);或者sel_registerName("test");需要注意的一点就是不同类中的同名方法,它们的方法选择器是相同的,比如A、B两个类中都有test方法,那么这两个test方法的名称都是@selector(test);或者sel_registerName("test");

  • types,表示方法的编码,即返回值、参数的类型,通过字符串拼接的方式将返回值和参数拼接成一个字符串,来代表函数返回值及参数。

比如viewDidLoad方法,我们都知道它的返回值是void,参数转为底层语言后是self和_cmd,即一个id类型和一个方法选择器,那么encode后就是v16@0:8(它所表示的意思是:返回值是void类型,参数一共占用16个字节,第一个参数是@类型,内存空间从0开始,第二个参数是:类型,内存空间从8开始),当然这里的数字可以不写,简写成V@:

  • 关于更多encode规则,可以查看下面这个表:

    05.png
NSLog(@"%s",@encode(int));
NSLog(@"%s",@encode(float));
NSLog(@"%s",@encode(id));
NSLog(@"%s",@encode(SEL));

// 打印内容
Runtime-test[25275:9144176] i
Runtime-test[25275:9144176] f
Runtime-test[25275:9144176] @
Runtime-test[25275:9144176] :
  • imp,表示指向函数的指针(函数地址),即方法的具体实现,我们调用的方法实际上最后都是通过这个imp去进行最终操作的。