一、Class的结构
- 通过查看源码, 可以得出Class的底层结构如下图
- 一开始
class_data_bits_t bits;
指向ro
, 在加载的过程中创建了rw
, 此时的指向顺序是bits->rw->ro
二、class_rw_t
class_rw_t
里面的methods
、properties
、protocols
是二维数组,是可读可写的,包含了类的初始内容、分类的内容
三、class_ro_t
class_ro_t
里面的baseMethodList
、baseProtocols
、ivars
、baseProperties
是一维数组,是只读的,包含了类的初始内容
四、method_t
method_t
是对方法\函数
的封装,method_t
结构如下
- IMP代表函数的具体实现
SEL
代表方法\函数名
,一般叫做选择器
,底层结构跟char *
类似- 可以通过
@selector()
和sel_registerName()
获得 - 可以通过
sel_getName()
和NSStringFromSelector()
转成字符串 - 不同类中相同名字的方法,所对应的方法选择器是相同的
- 可以通过
types
包含了函数返回值、参数编码的字符串
iOS
中提供了一个叫做@encode
的指令,可以将具体的类型表示成字符串编码
五、方法缓存
Class
内部结构中有个方法缓存(cache_t
),用散列表
(哈希表
)来缓存曾经调用过的方法,可以提高方法的查找速度
- 调用方法时的查找过程
以对象方法为例:
通过isa找到class
先会先从cache中查找, 如果有方法, 直接调用
如果没有方法, 在自身的class->bits->rw中查找方法
如果找到方法直接调动,并将方法缓存到cache中
如果没有找到, 会通过superclass找到父类, 从父类->bits->rw中查找方法
如果在父类中找到方法, 就直接调用, 同时将方法存到自己(不是父类的)的cache中, 如果一直找不到, 就进入下一个阶段
复制代码
散列表存储方法
- 一开始, 系统会分配给
cache_t->_buckets
一段内存, 假设第一次分配了足够存储3
个方法的内存
- 此时
cache_t
的mask
等于2
, 即_buckets的长度 - 1
- 当存储方法时, 会用
SEL & mask
, 获取到一个数字, 用这个数字做为索引, 将该方法存储到_buckets
中 - 假如有以下代码,
Person
继承自NSObject
, 有三个方法eat
、run
和say
Student
继承自Person
, 有三个方法learning
、exercises
和dancing
- 当一个
Student实例
调用learning
方法时, 就会用@selector(learning) & _buckets.mask
来获取存储的索引,然后将learning
方法存放到Student类对象
的cache
中 - 假设获取到的索引值为
0
, 那么散列表
的结构类似下图
- 如果
Student实例
调用父类Person
的eat
方法时, 根据@selector(eat) & _buckets.mask
的结果将becket_t
插入到相应位置 - 假设
@selector(eat) & _buckets.mask
结果是2
, 那么散列表
结构类似下图
-
即使
eat
是Person
中的方法, 但是Student
调用,也会存到Student
的cache
中 -
当多个数都
&
一个固定值时, 那么肯定就有重复的可能出现, 比如
1001 1010 1000 1011
&0000 1010 &0000 1010
----------- ------------
0000 1010 0000 1010
复制代码
- 所以通过多个
SEL & _buckets.mask
获取到的值就有可能是重复的 - 此时,存储方法的索引就会
-1
, 然后在查找-1
后所在位置是否空缺, 如果有空缺就会存储 - 如果
Student实例
调用exercises
方法, 会计算@selector(exercises) & _buckets.mask
的结果作为索引值 - 假设
@selector(exercises) & _buckets.mask
的结果是2
, 此时因为索引2
的位置已经存储eat
方法, 所以索引会-1
变成1
, 然后查看1
的位置是否空缺, 如果空缺就会存储, 如下图
- 如果
Student实例
继续调用dancing
方法, 此时cache->buckets
已经存储满 - 那么
buckets
的容量会扩大一倍, 容量变为6
,重新计算cache->mask
的值为5
, 接着清空buckets
中之前缓存的所有方法 - 然后计算
@selector(dancing) & _buckets.mask
的值, 如果此时的结果是3
, 那么散列表
的结构类似下图
- 因为已经清空过
cache
中的所有方法, 所以此时只存储dancing
方法
六、使用代码验证cache存储流程
准备代码
Person
继承自NSObject
, 有一个对象方法personTest
Student
继承自Person
, 有一个对象方法studentTest
GoodStudent
继承自Student
, 有一个对象方法goodStudentTest
方法调用后, 会缓存在cache中
main.mm
文件中有如下代码, 并在19
和21
行打断点
- 执行程序, 查看
personClass
的结构吗可以看到cache
中的_buckets
长度是_mask + 1 = 4
, 此时已经存储一个方法, 即刚调用过的init
方法
- 接下来过掉第一个断点, 可以看到
personTest
已经被存到了_buckets
中
- 下面使用代码查看更复杂的存储方式, 首先有如下代码, 在
19
,20
,21
,23
四处打上断点 - 执行程序, 可以看到此时
_buckets
中只存了一个方法
- 过掉
19
行断点, 可以看到有一个方法被存到_buckets
中, 这个方法就是goodStudentTest
- 过掉
20
行断点, 可以看到又一个方法被存到_buckets
中, 这个方法就是studentTest
, 此时_buckets
中已经存放了三个方法
- 接着过掉
21
行断点, 此时_buckets
进行了扩容,从新计算了mask
, 同时清空了缓存的方法, 并将新调用的personTest
存到了_buckets
中
打印cache中存储的方法
- 将
main
函数中的代码修改为如下所示
- 运行程序, 可以看到
cache->_buckets
中存储的方法, 其中包含了init
、goodStudentTest
和studentTest
- 在将代码修改为如下所示,
goodStudent
先调用5
个方法, 在打印缓存的方法列表
- 执行程序, 可以看到下面的打印
- 在上面已经知道, 在执行
[goodStudent personTest];
时已经进行了扩容, 所以此时的容量是8
, 由于清空了之前缓存的方法, 所以现在_buckets
中只存储了三个方法
通过索引获取方法
- 将
main
函数中的代码修改为如下所示
- 执行代码, 可以看到打印信息, 已经将
personTest
的方法和地址打印出来
- 我们可以通过控制台来验证打印的确实就是
personTest
方法
- 我们可以用同样的方式, 打印这三个方法
- 执行程序, 可以看到三个方法地址的打印
- 但是可以看到
personTest
和goodStudentTest
取出的地址是相同的 - 我们可以明确的知道这两个方法不同, 那么取出地址相同的原因就是
SEL & mask
得到的索引值相同 - 通过打印, 也可以看到索引值是相同的
- 之前已经说过, 不同的数字
&
一个固定的数, 肯定会出现重复的情况, 所以取出的方法也就会是同一个 - 此时取方法, 需要将索引
-1
然后再去取出方法, 如果取出的是空或者方法名不对, 那么就继续-1, 再去取方法, 以此类推
- 索引我们通过下面的方式, 就能取出缓存中正确的方法
- 执行程序, 可以看到取出的方法全部正确
MJClassInfo.h
文件中代码如下
#import <Foundation/Foundation.h>
#ifndef MJClassInfo_h
#define MJClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
#else
#error unknown architecture
#endif
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
IMP imp(SEL selector)
{
mask_t begin = _mask & (long long)selector;
mask_t i = begin;
do {
if (_buckets[i]._key == 0 || _buckets[i]._key == (long long)selector) {
return _buckets[i]._imp;
}
} while ((i = cache_next(i, _mask)) != begin);
return NULL;
}
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance对象占用的内存空间
#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;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 属性列表
const protocol_list_t * protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC对象 */
struct mj_objc_object {
void *isa;
};
/* 类对象 */
struct mj_objc_class : mj_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
mj_objc_class* metaClass() {
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* MJClassInfo_h */
复制代码