本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
背景
上一篇文章中笔者和大家分析了 maptable 的头文件。今天我们来看一下 maptable 的具体实现。
在开始分析之前,我们先全局搜索一下, maptable 几个方法的使用:
-
创建
//此 maptable 是为了存储 需要调用 initialize 的方法?
pendingInitializeMap =
NXCreateMapTable(NXPtrValueMapPrototype, 10);
//创建了 category_map 是为了存储所有分类?
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
// nonmeta_class_map is typically small
INIT_ONCE_PTR(nonmeta_class_map,
NXCreateMapTable(NXPtrValueMapPrototype, 32),
NXFreeMapTable(v));
// future_named_class_map is big enough for CF's classes and a few others
future_named_class_map =
NXCreateMapTable(NXStrValueMapPrototype, 32);
// remapped_class_map is big enough to hold CF's classes and a few others
INIT_ONCE_PTR(remapped_class_map,
NXCreateMapTable(NXPtrValueMapPrototype, 32),
NXFreeMapTable(v));
INIT_ONCE_PTR(protocol_map,
NXCreateMapTable(NXStrValueMapPrototype, 16),
NXFreeMapTable(v) );
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
由以上代码可以看出,runtime 中一共使用了7个 maptable :
pendingInitializeMap 、 category_map 、nonmeta_class_map 、future_named_class_map 、remapped_class_map 、protocol_map 、gdb_objc_realized_classes
这些 maptable 的作用这里先不展开讲了,因为每个 maptable 都涉及到非常多的逻辑。目前大家有个大概认识就好。
分析
写一个方法,如下:
void demoMethod(void)
{
NXMapTable *maptable = NXCreateMapTable(NXPtrValueMapPrototype, 16);
int a = 1000;
int* p = &a ;
int* a1 = (int *)NXMapGet(maptable, "kyson");
printf("%p",a1);
NXMapInsert(maptable, "kyson", p);
int * b = (int *)NXMapGet(maptable, "kyson");
int b1 = *b;
printf("%i",b1);
NXMapRemove(maptable,"kyson");
int *c = (int *)NXMapGet(maptable, "kyson");
printf("%p",c);
}
这段代码覆盖了创建、插入、移除、获取操作,基本可以满足我们的需求了。那么这段代码放在哪里能运行呢?由于 main 文件里缺少相应头文件,因此笔者放在了这里:
或者还是使用笔者剥离出来的项目:https://github.com/zjh171/RuntimeSample
下面我们一个一个分析:
创建
NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) {
NXMapTable *table = (NXMapTable *)malloc_zone_malloc((malloc_zone_t *)z, sizeof(NXMapTable));
NXMapTablePrototype *proto;
if (! prototypes)
prototypes = NXCreateHashTable(protoPrototype, 0, NULL);
if (! prototype.hash || ! prototype.isEqual || ! prototype.free || prototype.style) {
_objc_inform("*** NXCreateMapTable: invalid creation parameters\n");
return NULL;
}
proto = (NXMapTablePrototype *)NXHashGet(prototypes, &prototype);
if (! proto) {
proto = (NXMapTablePrototype *)malloc(sizeof(NXMapTablePrototype));
*proto = prototype;
(void)NXHashInsert(prototypes, proto);
}
table->prototype = proto;
table->count = 0;
table->nbBucketsMinusOne = exp2u(log2u(capacity)+1) - 1;
table->buckets = allocBuckets(z, table->nbBucketsMinusOne + 1);
return table;
}
在这个方法中,绝大多数代码都是用来初始化 table->prototype 的,我们先把这部分全部忽略,分析一下简略版本的实现。
NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) {
NXMapTable *table = (NXMapTable *)malloc_zone_malloc((malloc_zone_t *)z, sizeof(NXMapTable));
NXMapTablePrototype *proto;
...
table->prototype = proto;
table->count = 0;
table->nbBucketsMinusOne = exp2u(log2u(capacity)+1) - 1;
table->buckets = allocBuckets(z, table->nbBucketsMinusOne + 1);
return table;
}
其他的没什么好说的,nbBucketsMinusOne
有点特殊,
调用了两个函数:
static unsigned log2u(unsigned x) { return (x<2) ? 0 : log2u (x>>1)+1; };
static INLINE unsigned exp2u(unsigned x) { return (1 << x); };
这两个函数看名字我们就能猜到,一个是对数,一个是指数:
对数:
logarithm 英 [ˈlɒgərɪðəm] 美 [ˈlɔ:gərɪðəm]
指数:
exponent 英 [ɪkˈspəʊnənt] 美 [ɪkˈspoʊnənt]
继续,我们分析这两个函数的代码: log2u 函数是个递归实现,其小于 2 则右移一位,而 exp2u 则相反,是左移一位,那么左移右移有什么区别呢?
移位操作符
<<:左移运算,与其对应的有 >> (右移) 。实现过程是把该变量先变成2进制数,然后进行移位,在用0补齐。
右移一位,其实就是 除以 2 的操作。比如如果开始时 a = 10,那么 a>>1 后,a = 5。
左移一位,其实就是 乘以 2 的操作。比如如果开始时 a = 10,那么 a>>1 后,a = 20。
nbBuckets到这里就分析完成了,很简单,大家不用多去考虑,其实就是一个扩容算法吧。笔者的另外一个算法专栏里有对扩容做过详细的解释,这里就点到为止了。
nbBucketsMinusOne
名字也有意思:nbBuckets 减去 1。
获取 table->nbBuckets
之后,再初始化 table->nbBucketsMinusOne + 1 大小的内存空间。
NXMapTablePrototype 上篇文章已经讲解过了,存储了 hash、isEqual 和 free 的函数指针(用于获取数据的哈希、判断两个数据是否相等以及释放数据)。
buckets 存放了真正的数据,赋值的时候有个方法: allocBuckets,其源代码如下:
static INLINE void *allocBuckets(void *z, unsigned nb) {
MapPair *pairs = 1+(MapPair *)malloc_zone_malloc((malloc_zone_t *)z, ((nb+1) * sizeof(MapPair)));
MapPair *pair = pairs;
while (nb--) { pair->key = NX_MAPNOTAKEY; pair->value = NULL; pair++; }
return pairs;
}
而 MapPair 的定义如下:
typedef struct _MapPair {
const void *key;
const void *value;
} MapPair;
可以看出,这里分配的 Buckets 大小其实就是 MapPair 的个数乘以每一个 MapPair 大小的内存空间。创建相应对象后,再讲 key 值为 NX_MAPNOTAKEY ,value 置为 NULL 。
由于 malloc_zone_malloc 分配的是一块连续的内存,所以,我们可以理解为 allocBuckets 方法分配了相应数量的 MapPair,每个 MapPair 空间上是连续的,也就是说我们可以把 nbBuckets 看做一个 MapPair 的数组。
本文详细版请见:
https://xiaozhuanlan.com/topic/4352197806
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。