
本系列博客是本人的源碼閱讀筆記,如果有 iOS 開發(fā)者在看 runtime 的,歡迎大家多多交流。為了方便討論,本人新建了一個(gè)微信群(iOS技術(shù)討論群),想要加入的,請(qǐng)?zhí)砑颖救宋⑿牛簔hujinhui207407,【加我前請(qǐng)備注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論
本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime
背景
上一篇文章中筆者和大家分析了 maptable 的頭文件。今天我們來看一下 maptable 的具體實(shí)現(xiàn)。
在開始分析之前,我們先全局搜索一下, maptable 幾個(gè)方法的使用:
-
創(chuàng)建
//此 maptable 是為了存儲(chǔ) 需要調(diào)用 initialize 的方法?
pendingInitializeMap =
NXCreateMapTable(NXPtrValueMapPrototype, 10);
//創(chuàng)建了 category_map 是為了存儲(chǔ)所有分類?
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個(gè) maptable :
pendingInitializeMap 、 category_map 、nonmeta_class_map 、future_named_class_map 、remapped_class_map 、protocol_map 、gdb_objc_realized_classes
這些 maptable 的作用這里先不展開講了,因?yàn)槊總€(gè) maptable 都涉及到非常多的邏輯。目前大家有個(gè)大概認(rèn)識(shí)就好。
分析
寫一個(gè)方法,如下:
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);
}
這段代碼覆蓋了創(chuàng)建、插入、移除、獲取操作,基本可以滿足我們的需求了。那么這段代碼放在哪里能運(yùn)行呢?由于 main 文件里缺少相應(yīng)頭文件,因此筆者放在了這里:

或者還是使用筆者剝離出來的項(xiàng)目:https://github.com/zjh171/RuntimeSample

下面我們一個(gè)一個(gè)分析:
創(chuàng)建
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;
}
在這個(gè)方法中,絕大多數(shù)代碼都是用來初始化 table->prototype 的,我們先把這部分全部忽略,分析一下簡(jiǎn)略版本的實(shí)現(xiàn)。
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 有點(diǎn)特殊,
調(diào)用了兩個(gè)函數(shù):
static unsigned log2u(unsigned x) { return (x<2) ? 0 : log2u (x>>1)+1; };
static INLINE unsigned exp2u(unsigned x) { return (1 << x); };
這兩個(gè)函數(shù)看名字我們就能猜到,一個(gè)是對(duì)數(shù),一個(gè)是指數(shù):
對(duì)數(shù):
logarithm 英 [?l?g?r?e?m] 美 [?l?:g?r?e?m]
指數(shù):
exponent 英 [?k?sp??n?nt] 美 [?k?spo?n?nt]
繼續(xù),我們分析這兩個(gè)函數(shù)的代碼: log2u 函數(shù)是個(gè)遞歸實(shí)現(xiàn),其小于 2 則右移一位,而 exp2u 則相反,是左移一位,那么左移右移有什么區(qū)別呢?
移位操作符
<<:左移運(yùn)算,與其對(duì)應(yīng)的有 >> (右移) 。實(shí)現(xiàn)過程是把該變量先變成2進(jìn)制數(shù),然后進(jìn)行移位,在用0補(bǔ)齊。
右移一位,其實(shí)就是 除以 2 的操作。比如如果開始時(shí) a = 10,那么 a>>1 后,a = 5。
左移一位,其實(shí)就是 乘以 2 的操作。比如如果開始時(shí) a = 10,那么 a>>1 后,a = 20。
nbBuckets到這里就分析完成了,很簡(jiǎn)單,大家不用多去考慮,其實(shí)就是一個(gè)擴(kuò)容算法吧。筆者的另外一個(gè)算法專欄里有對(duì)擴(kuò)容做過詳細(xì)的解釋,這里就點(diǎn)到為止了。
nbBucketsMinusOne名字也有意思:nbBuckets 減去 1。
獲取 table->nbBuckets 之后,再初始化 table->nbBucketsMinusOne + 1 大小的內(nèi)存空間。
NXMapTablePrototype 上篇文章已經(jīng)講解過了,存儲(chǔ)了 hash、isEqual 和 free 的函數(shù)指針(用于獲取數(shù)據(jù)的哈希、判斷兩個(gè)數(shù)據(jù)是否相等以及釋放數(shù)據(jù))。
buckets 存放了真正的數(shù)據(jù),賦值的時(shí)候有個(gè)方法: 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 大小其實(shí)就是 MapPair 的個(gè)數(shù)乘以每一個(gè) MapPair 大小的內(nèi)存空間。創(chuàng)建相應(yīng)對(duì)象后,再講 key 值為 NX_MAPNOTAKEY ,value 置為 NULL 。
由于 malloc_zone_malloc 分配的是一塊連續(xù)的內(nèi)存,所以,我們可以理解為 allocBuckets 方法分配了相應(yīng)數(shù)量的 MapPair,每個(gè) MapPair 空間上是連續(xù)的,也就是說我們可以把 nbBuckets 看做一個(gè) MapPair 的數(shù)組。
本文詳細(xì)版請(qǐng)見:
https://xiaozhuanlan.com/topic/4352197806
廣告
我的首款個(gè)人開發(fā)的APP壁紙寶貝上線了,歡迎大家下載。

