iOS开发之runtime(25):maptable之增删查改实现

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

背景

上一篇文章中笔者和大家分析了 maptable 的头文件。今天我们来看一下 maptable 的具体实现。

在开始分析之前,我们先全局搜索一下, maptable 几个方法的使用:

  1. 创建


//此 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壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

你可能感兴趣的:(iOS开发之runtime(25):maptable之增删查改实现)