iOS底层(四)-类的结构分析

285 阅读4分钟

1. 类的结构是什么

在开发中,经常会自己去写一些类,但是类究竟是一个什么样的结构?

打开一份objc4-750源码,创建一个测试类 MyTest. 在main.h中实例化

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyTest *person = [MyTest alloc];
    }
    return 0;
}

在终端对main.h进行编译, 命令如下:

clang -rewrite-objc main.m -o main.cpp

得到一份遍以后的c++文件 main.cpp

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MyTest *person = ((MyTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyTest"), sel_registerName("alloc"));
    }
    return 0;
}

这就是在main函数编译后的样子.但是重点并不是这里, 我们探索的是类的结构.所以就要找与class相关的东西.(因为万物都可以用Class来接收)

会发现这样的一个Class

typedef struct objc_class *Class;

在750源码的objc-runtime-new.h里找到objc_class:

从中得知, objc_class也是一个继承objc_object的结构体, 所以类也是一个对象.

其中还有一个隐藏的Class ISA; 那么这个ISA必然是继承自objc_object. 这也就是为什么一个对象内存中的第一个位置就是isa的原因 进入objc_object:

但是有人或许会问,万物不是继承NSObjcet的吗? 那么我们就来找一下NSObject的结构:

与objc_object结构是一模一样的.

OC底层实际上就是对C的一层封装

2.类的属性与方法

2.1属性与成员变量

在之前的MyTest添加一个成员变量与属性

@interface MyTest : NSObject
{
    NSString *testStr;
}

@property (nonatomic, strong) NSString *testStr2;

@end

运行后打印出这个类

根据上面的objc_class结构得知:

0x001d800100001241: 代表isa

0x0000000100afe140: 代表superclass

接下来就是cache, 进入cache_t看一下结构:

那么cache_t的大小就是。8(bucket_t指针) + 4(mask_t) + 4(mask_t) = 16个字节

那么可以得到: 0x00000001003a0e70 0x0000000000000000 :代表cache

通过上文看到objc_class有一个data()方法, 返回的是class_rw_t类型,看一下结构

根据命名,可以猜测这些东西就是我们想要的东西.

根据之前的操作,对内存进行偏移,可以知道,一个类的首地址向右偏移32个字节就是bits的指针了. p一下bits指针

根据data()方法的方法实现, 对$9指针进行调用data()方法得到一个class_rw_t结构体指针:

看一下$11的内容

找到properties:

尝试一下list:

这里面也可以看到属性. 通过修改属性名增加额外属性做验证

可以看到两个属性都可以找到, 但是却在class_rw_t中找不到相对应的成员变量. 那么肯定是存在另外一个地方, 根据命名来看,其他的都不是很符合. 唯有一个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;
    }
};

继续上面的调试进入ro:

有一个baseProperties 打印出来

得到属性. 但是还说没有成员变量,它在哪里呢? 我们注意到class_ro_t中有一个ivars,打印出来

得到成员变量! 同理,

也有get方法,通过get方法可以取出其他成员变量.这部分不再重复验证.

2.2类的方法

在上面我们同样看到有一个 baseMethods, 打印出来:

count=5, 代表着有5个方法,但是按照上文来看, 在MyTest里面没有任何方法. 通过get()方法查看具体方法:

这下一目了然. 属性在编译阶段会自动生成set get方法,两个属性一起四个方法, 外加一个cxx方法(暂时不做分析). 在MyTest中添加方法:

@interface MyTest : NSObject
{
    NSString *testStr;
}
@property (nonatomic, strong) NSString *testStr2_rename;
@property (nonatomic, strong) NSString *testStr3;

- (void)add;
+ (void)class_add;

@end

重复步骤略.

发现一个问题, 只有实例方法没有类方法. 类方法不在本类中, 可能在父类中,但是父类NSObject肯定是不可能存在的, 那么是否可能存在元类中呢?

由此可以得到一些结论:

  1. 类的属性、成员变量、实例方法、协议等都会存储在class_ro_t这结构体中
  2. class_rw_t中也会存放类的属性、实例方法以及协议。
  3. 类方法是存储在元类中的