1、Class的结构
类对象(class)是程序员定义并在运行时由编译器创建的。
通过对类对象进行alloc、new操作创建出实例对象(instance)。
元类对象(meta-class)是类对象的类,是类对象中isa指针所指的类。

在源码中,Class是一个指向objc_class结构体的指针。
typedef struct objc_class *Class;
typedef struct objc_object *id;
他的内部结构如下:

class_rw_t
class_rw_t:rw表示可读写
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。

class_ro_t
class_ro_t:ro表示只读
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。

一开始,类的结构里class_rw_t没有内容。在后续过程中,会将class_ro_t里的初始内容拷贝到class_rw_t中,如果有新增分类,分类里的内容也会被写入到class_ro_t中。
待完善
2、方法的结构method_t
Method是一种代表类中的某个方法的类型。
typedef struct method_t *Method;
method_t就是对方法的封装,在上面class_rw_t、class_ro_t中的方法列表里,都含有结构体method_t,它存储了方法名、方法类型和方法实现。
struct method_t {
SEL name; //方法名、函数名
const char *types; //编码(返回值类型、参数类型)
IMP imp; //指向函数的指针(函数地址)
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
SEL
typedef struct objc_selector *SEL;
代表方法名,一般叫做选择器,底层结构跟char *类似;
可以通过@selector()和sel_registerName()获得;
可以通过sel_getName()和NSStringFromSelector()转成字符串;
相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
types

方法类型 types 是个char指针,存储着方法的参数类型和返回值类型。
types里存贮的内容是encode每个方法的返回值、参数类型,将这些信息保存在一个字符串里,再将这个string与selector关联起来。
(涉及到的知识点:Type Encodings)。
IMP
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
它就是一个函数指针,这是由编译器生成的。
当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的,而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。
IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id和 SEL参数就能确定唯一的方法实现地址。
3、方法缓存
在调用方法时会根据对象的isa指针找到类对象,在类对象的列表(class_rw_t中的methods列表)里找对象方法,如果找不到会通过superClass指针找到父类的类对象,然后在其方法列表中查找。
为了节省时间,苹果在objc_class结构体中设计了一个cache用来缓存调用过的方法。当第一次调用某个方法时,找到该方法后,会将这个方法存放到cache中,下次再调用该方法时,会先在类对象isa中的cache中找方法。
struct cache_t {
struct bucket_t *_buckets; // 散列表,数组里面放的是bucket_t这个结构
mask_t _mask; //散列表的长度-1
mask_t _occupied; //已经缓存的方法数量
//...more code...
};
struct bucket_t {
cache_key_t _key; //SEL作为Key
IMP _imp; //函数的内存地址
//... more code ...
};
chche_t的结构如上,通过散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
具体做法是用@selector() & _mask
得到一个索引值,根据这个索引去散列表中找到方法的_imp,如果查找结果不对,会索引值减1,再对比方法。由于_mask的值为散列表长度减1,这样保证查找范围不会超过散列表范围。而且当散列表不够存储时,散列表容量扩大一倍并清除内容再重新缓存方法。
网友评论