一 :分析类的内部结构
第一步:写一个Damon,生成一个NSObject的类。xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc model.m -o model.cpp 使用终端编译出C++文件。如图:
第二步在:mian.cpp文件里面可以看出来:
一:类对象的结构
一.objc_object的结构如下
struct objc_class : objc_object {
// Class ISA; (父类里面有ISA)
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags //& FAST_DATA_MASK 取出来掩码的存储的数据;
class_rw_t *data() {
return bits.data();
}
二.class_rw_t的内部结构
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; //二维数组;放着分类和类对象的方法
property_array_t properties;//二维数组;放着分类和类对象的属性
protocol_array_t protocols;//二维数组;放着分类和类对象的协议
Class firstSubclass;
Class nextSiblingClass;
}
arrary_t 一般都是二维数组;list_t都是一维数组;
class_rw_t 里面的methods、perperties、pertocols是二维数组,是可读可写的,包含了类的初始化内容和分类的内容;
class_ro_t 里面的baseMethodList、baseProperties、baseProtocols、ivars是二维数组,是只读的,只是包含了类的初始化内容;
arrary_t 里面的元素都是method_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;
}
};
四:method_t的结构:
struct method_t {
SEL name; //函数名;选择器底层类似于char*类型;
const char *types;//编码(返回值类型,参数类型)
IMP imp;指向函数的指针(函数地址);
};
1.imp
1)、imp代表函数的具体实现;
typedef id _Nullable (*IMP)(id _Nullable,SEL _Nommull,..... );
2)、SEL代表方法\函数名,一般叫做选择器,底层结构跟char*类似;
1)、方法一: sel_registerName(<#const char * _Nonnull str#>) 获得SEL
方法二:@selector(<#selector#>)获得SEL
2)、可以通过 sel_getName(<#SEL _Nonnull sel#>)、NSStringFromSelector(<#SEL _Nonnull aSelector#>)转成字符串
注意:不同类中相同名的方法,所对应的方法选择器是相同的;@selector(test) 地址是一样的;
3)、types包含了函数返回值,参数编码的字符串
1). 内存分配:
返回值 参数1 参数2 .... 参数N
对应了iOS的一个映射表;int- i id -@ 后面是占用的长度;
iOS 中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码
NSLog(@"%s",@encode(SEL));
五:方法缓存 cache_t
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; //散列表的长度减1
mask_t _occupied;//已经缓存的方法数量;
};
struct bucket_t {
private:
cache_key_t _key; // sel 作为key
IMP _imp; //_imp // 函数的内存地址;
};
class 内部结构中有个方法缓存(cache_t),用散列表来缓存曾经调用过的方法,可以提高方法的查找速度;
cache_t的底层是一个散列表;
六:散列表(哈希表)
本质是数组和链表的结合体
0 bucket_t(key,_imp)
1 bucket_t(key,_imp)
2 bucket_t(key,_imp)
3 bucket_t(key,_imp)
4 bucket_t(key,_imp)
5 bucket_t(key,_imp)
1. 取值: 拿到key&_mask = 生成一个值 = 索引
2. 存值: 拿到key&_mask = 生成一个值,存入这值相应的位置;所以会出现一些空值;
以牺牲空间换取时间的算法;
1001 1100
&00 0000 1000
-----------------------
00 0000 1000
&_mask 位于以后的结果小于等于 _mask的值;所以_mask的是散列表的长度减去1;
f(key) == index ;通过key。传入函数中求出数组的索引;如果出现冲突,就让算出来的值,减一;如果还是不行再减一;