上古時代 Objective-C 中哈希表的實現

關注倉庫,及時得到更新:iOS-Source-Code-Analyze
Follow: Draveness · Githubgit

由於 ObjC 的 runtime 只能在 Mac OS 下才能編譯,因此文章中的代碼都是在 Mac OS,也就是 x86_64 架構下運行的,對於在 arm64 中運行的代碼會特別說明。github

寫在前面

文章會介紹上古時代 Objective-C 哈希表,也就是 NXHashTablebootstrap

  • NXHashTable 的實現數組

  • NXHashTable 的性能分析數據結構

  • NXHashTable 的做用架構

NXHashTable 的實現有着將近 30 年的歷史,不過仍然做爲重要的底層數據結構存儲整個應用中的類。函數

文中會涉及一些數據結構方面的簡單知識,例如拉鍊法性能

注意:文章中分析的不是 NSHashTable 而是 NXHashTable測試

NXHashTable

NXHashTable 的實現位於 hashtable2.mm 文件,咱們先來看一下 NXHashTable 的結構以及重要的接口:ui

typedef struct {
    const NXHashTablePrototype *prototype;
    unsigned count;
    unsigned nbBuckets;
    void *buckets;
    const void *info;
} NXHashTable;

對於結構體中的 NXHashTablePrototype 屬性暫且不說,其中的 buckets 是真正用來存儲數據的數組

NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z);
unsigned NXCountHashTable (NXHashTable *table);
int NXHashMember (NXHashTable *table, const void *data);
void *NXHashGet (NXHashTable *table, const void *data);
void *NXHashInsert (NXHashTable *table, const void *data);
void *NXHashRemove (NXHashTable *table, const void *data);

咱們會以上面的這些方法做爲切入點,分析 NXHashTable 的實現。

NXCreateHashTableFromZone

NXHashTable 使用 NXCreateHashTableFromZone 方法初始化:

NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z) {
    NXHashTable            *table;
    NXHashTablePrototype     *proto;

    table = ALLOCTABLE(z);
    if (! prototypes) bootstrap ();
    if (! prototype.hash) prototype.hash = NXPtrHash;
    if (! prototype.isEqual) prototype.isEqual = NXPtrIsEqual;
    if (! prototype.free) prototype.free = NXNoEffectFree;

    proto = (NXHashTablePrototype *)NXHashGet (prototypes, &prototype);
    if (! proto) {
        proto = (NXHashTablePrototype *) malloc(sizeof (NXHashTablePrototype));
        bcopy ((const char*)&prototype, (char*)proto, sizeof (NXHashTablePrototype));
        (void) NXHashInsert (prototypes, proto);
        proto = (NXHashTablePrototype *)NXHashGet (prototypes, &prototype);
    };
    table->prototype = proto;
    table->count = 0;
    table->info = info;
    table->nbBuckets = GOOD_CAPACITY(capacity);
    table->buckets = ALLOCBUCKETS(z, table->nbBuckets);
    return table;
}

在這個方法中,絕大多數代碼都是用來初始化 table->prototype 的,咱們先把這部分所有忽略,分析一下簡略版本的實現。

NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z) {
    NXHashTable            *table;
    NXHashTablePrototype     *proto;

    table = ALLOCTABLE(z);
    
    ...

    table->count = 0;
    table->info = info;
    table->nbBuckets = GOOD_CAPACITY(capacity);
    table->buckets = ALLOCBUCKETS(z, table->nbBuckets);
    return table;
}

其中 ALLOCTABLEGOOD_CAPACITY 以及 ALLOCBUCKETS 都是用來輔助初始化的宏:

#define     ALLOCTABLE(z) ((NXHashTable *) malloc_zone_malloc ((malloc_zone_t *)z,sizeof (NXHashTable)))
#define GOOD_CAPACITY(c) (exp2m1u (log2u (c)+1))
#define ALLOCBUCKETS(z,nb) ((HashBucket *) malloc_zone_calloc ((malloc_zone_t *)z, nb, sizeof (HashBucket)))

ALLOCTABLEALLOCBUCKETS 只是調用了 malloc_zone_calloc 來初始化相應的結構體,而 GOOD_CAPACITY 有一些特殊,咱們來舉個例子說明:

c   binary  result
1   1       1 
2   10      3(0b11)
6   110     7(0b111)
100 1100100 127(0b111 1111)

c 表示傳入參數,binary 表示二進制下的參數,而 result 就是 GOOD_CAPACITY 返回的結果。

每次返回當前位數下的二進制最大值。

得到 table->nbBuckets 以後,再初始化 table->nbBuckets * sizeof (HashBucket) 大小的內存空間。

NXHashTablePrototype

在繼續分析其它方法以前,咱們須要先知道 NXHashTablePrototype 是什麼:

typedef struct {
    uintptr_t (*hash)(const void *info, const void *data);
    int (*isEqual)(const void *info, const void *data1, const void *data2);
    void (*free)(const void *info, void *data);
    int style; /* reserved for future expansion; currently 0 */
} NXHashTablePrototype;

NXHashTablePrototype 中存儲了 hashisEqualfree 的函數指針(用於獲取數據的哈希、判斷兩個數據是否相等以及釋放數據)。

hashtable2.mm 文件中有一個宏 ISEQUAL 就是用了 NXHashTablePrototype 中的 isEqual 來判斷兩個數據是否相等:

#define ISEQUAL(table, data1, data2) ((data1 == data2) || (*table->prototype->isEqual)(table->info, data1, data2))

能夠說,NXHashTablePrototype 中存儲了一些構建哈希表必要的函數指針

由於 NXHashTable 使用拉鍊法來實現哈希表,在存入表前對數據執行 hash,而後找到對應的 buckets,若是與 buckets 中的數據相同(使用 isEqual 判斷),就替換原數據,不然將數據添加到鏈表中。

HashBucket

在這裏另外一個須要注意的數據結構就是 HashBucket

typedef struct    {
    unsigned count;
    oneOrMany elements;
} HashBucket;

oneOrMany 是一個 union 結構體:

typedef union {
    const void *one;
    const void **many;
} oneOrMany;

這麼設計的主要緣由是提高性能

若是 HashBucket 中只有一個元素,那麼就直接訪問 one,不然訪問 many,遍歷這個 many 列表。

NXCountHashTable

NXCountHashTable 方法應該是咱們要介紹的方法中的最簡單的一個,它會直接返回 NXHashTable 結構體中的 count

unsigned NXCountHashTable (NXHashTable *table) {
    return table->count;
}

NXHashMember

NXHashMember 的函數簽名雖然會返回 int,其實它是一個布爾值,會判斷當前的 NXHashTable 中是否包含傳入的數據:

int NXHashMember (NXHashTable *table, const void *data) {
    HashBucket    *bucket = BUCKETOF(table, data);
    unsigned    j = bucket->count;
    const void    **pairs;

    if (! j) return 0;
    if (j == 1) {
        return ISEQUAL(table, data, bucket->elements.one);
    };
    pairs = bucket->elements.many;
    while (j--) {
        if (ISEQUAL(table, data, *pairs)) return 1;
        pairs ++;
    };
    return 0;
}

使用 BUCKETOFdata 進行 hash,將結果與哈希表的 buckets 數取模,返回 buckets 數組中對應的 NXHashBucket

#define BUCKETOF(table, data) (((HashBucket *)table->buckets)+((*table->prototype->hash)(table->info, data) % table->nbBuckets))

在獲取了 bucket 以後,根據其中元素個數的不一樣,選擇不一樣的分支:

if (! j) return 0;
if (j == 1) {
    return ISEQUAL(table, data, bucket->elements.one);
};
pairs = bucket->elements.many;
while (j--) {
    if (ISEQUAL(table, data, *pairs)) return 1;
    pairs ++;
};
  • count == 0,直接返回

  • count == 1,使用 ISEQUAL 比較查找的數據與 bucket->elements.one

  • count > 1,依次與 bucket->elements.many 中的值進行比較

    > 你可能以爲到這裏的時間複雜度比較糟糕,然而這個列表並不會很長,具體會在 [NXHashInsert](#nxhashinsert) 中解釋。

NXHashGet

其實我一直以爲這個方法可能用處不是很大,尤爲是在使用默認的 NXHashTablePrototype 時,由於默認的 NXHashTablePrototype 中的 isEqual 函數指針只是比較兩個數據的指針是否相同。

其最大做用就是查看當前 data 是否是在表中。

若是當前數據在表中,那麼這個方法只會返回一個相同的指針,沒有太多的意義。

它的實現跟上面的 NXHashMember 區別並不大,這裏就不過多介紹了:

void *NXHashGet (NXHashTable *table, const void *data) {
    HashBucket    *bucket = BUCKETOF(table, data);
    unsigned    j = bucket->count;
    const void    **pairs;

    if (! j) return NULL;
    if (j == 1) {
        return ISEQUAL(table, data, bucket->elements.one)
        ? (void *) bucket->elements.one : NULL;
    };
    pairs = bucket->elements.many;
    while (j--) {
        if (ISEQUAL(table, data, *pairs)) return (void *) *pairs;
        pairs ++;
    };
    return NULL;
}

NXHashInsert

NXHashInsertNXHashTable 中比較重要的方法,其做用就是向表中插入數據:

void *NXHashInsert (NXHashTable *table, const void *data) {
    HashBucket *bucket = BUCKETOF(table, data);
    unsigned j = bucket->count;
    const void **pairs;
    const void **newt;

    if (! j) {
        bucket->count++;
        bucket->elements.one = data;
        table->count++;
        return NULL;
    };
    if (j == 1) {
        if (ISEQUAL(table, data, bucket->elements.one)) {
            const void *old = bucket->elements.one;
            bucket->elements.one = data;
            return (void *) old;
        };
        newt = ALLOCPAIRS(z, 2);
        newt[1] = bucket->elements.one;
        *newt = data;
        bucket->count++;
        bucket->elements.many = newt;
        table->count++;
        if (table->count > table->nbBuckets) _NXHashRehash (table);
        return NULL;
    };
    pairs = bucket->elements.many;
    while (j--) {
        if (ISEQUAL(table, data, *pairs)) {
            const void    *old = *pairs;
            *pairs = data;
            return (void *) old;
        };
        pairs ++;
    };
    newt = ALLOCPAIRS(z, bucket->count+1);
    if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(newt+1), bucket->count * PTRSIZE);
    *newt = data;
    FREEPAIRS (bucket->elements.many);
    bucket->count++; 
    bucket->elements.many = newt;
    table->count++;
    if (table->count > table->nbBuckets) _NXHashRehash (table);
    return NULL;
}

雖然這裏的實現比上面的兩個方法複雜得多,可是脈絡仍然很清晰,咱們將插入的過程分爲三種狀況:

  • bucket->count == 0

  • bucket->count == 1

  • bucket->count > 1

若是對應的 bucket 爲空:

if (! j) {
    bucket->count++; 
    bucket->elements.one = data;
    table->count++;
    return NULL;
};

將數據直接填入 bucket,增長 bucket 中元素的數目,以及 table 中存儲的元素的數目:

objc-hashtable-insert-empty

若是原來的 buckets 中有一個元素,它會替換或者使用 many 替換原來的 one

if (j == 1) {
    if (ISEQUAL(table, data, bucket->elements.one)) {
        const void    *old = bucket->elements.one;
        bucket->elements.one = data;
        return (void *) old;
    };
    newt = ALLOCPAIRS(z, 2);
    newt[1] = bucket->elements.one;
    *newt = data;
    bucket->count++;
    bucket->elements.many = newt;
    table->count++;
    
    ...

    return NULL;
};

當前數據 data 若是與 bucket 中存儲的數據相同,就會更新這個數據,不然就會使用 ALLOCPAIRS 初始化一個新的數組,而後將 data 和原來的數據傳入。

objc-hashtable-insert-one.gif

可是若是原來的 bucket 中存儲的元素大於 1,那麼會在鏈表的頭部追加一個新的元素:

while (j--) {
    if (ISEQUAL(table, data, *pairs)) {
        const void    *old = *pairs;
        *pairs = data;
        return (void *) old;
    };
    pairs ++;
};
newt = ALLOCPAIRS(z, bucket->count+1);
if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(newt+1), bucket->count * PTRSIZE);
*newt = data;
FREEPAIRS (bucket->elements.many);
bucket->count++;
bucket->elements.many = newt;
table->count++;

上面的代碼使用 bcopy 將原鏈表中元素拷貝到新的數組 newt 中。

objc-hashtable-insert-many.gif

在每次添加完一個元素以後,都會進行下面的判斷:

if (table->count > table->nbBuckets) _NXHashRehash (table);

上面的這行代碼會保證哈希表中的元素數據小於等於表中的 bucket 數量

這就是 buckets 後面的列表很是短的緣由,在理想狀況下,每個 buckets 中都只存儲一個或零個元素

_NXHashRehash

若是哈希表在添加元素後,其中的數據多於 buckets 數量,就會對 NXHashTable 進行 _NXHashRehash 操做。

static void _NXHashRehash (NXHashTable *table) {
    _NXHashRehashToCapacity (table, MORE_CAPACITY(table->nbBuckets));
}

它調用 _NXHashRehashToCapacity 方法來擴大 NXHashTable 的容量(HashBucket 的個數)。

#define MORE_CAPACITY(b) (b*2+1)

MORE_CAPACITY 會將當前哈希表的容量翻倍,並將新的容量傳入 _NXHashRehashToCapacity 中:

void _NXHashRehashToCapacity (NXHashTable *table, unsigned newCapacity) {
    NXHashTable    *old;
    NXHashState    state;
    void    *aux;
    __unused void *z = ZONE_FROM_PTR(table);

    old = ALLOCTABLE(z);
    old->prototype = table->prototype; old->count = table->count;
    old->nbBuckets = table->nbBuckets; old->buckets = table->buckets;
    table->nbBuckets = newCapacity;
    table->count = 0; table->buckets = ALLOCBUCKETS(z, table->nbBuckets);
    state = NXInitHashState (old);
    while (NXNextHashState (old, &state, &aux))
        (void) NXHashInsert (table, aux);
    freeBuckets (old, NO);
    
    free (old->buckets);
    free (old);
}
  1. 建立一個 NXHashTable 的指針指向原哈希表

  2. 改變哈希表的 nbBuckets,並從新初始化哈希表的 buckets 數組

  3. 從新將元素插入到哈希表中

  4. 釋放原哈希表 old 以及 buckets

NXHashState

在將元素從新插入到哈希表中涉及了一個很是奇怪的結構體 NXHashState,這個結構體主要做用是遍歷 NXHashTable 中的元素。

typedef struct {
    int i;
    int j;
} NXHashState;

咱們可使用以下的代碼對哈希表中的元素進行遍歷:

unsigned count = 0;
 MyData     *data;
 NXHashState state = NXInitHashState(table);
 while (NXNextHashState(table, &state, &data)) {
    count++;
 }

代碼片斷中調用了兩個方法,分別是 NXInitHashState 以及 NXNextHashState

NXHashState NXInitHashState (NXHashTable *table) {
    NXHashState    state;

    state.i = table->nbBuckets;
    state.j = 0;
    return state;
};

NXInitHashState 會將 NXHashState 指向哈希表的最末端:

objc-hashtable-hash-state-init

這個位置其實並不屬於 NXHashTable,它必定會爲空。

而每次調用 NXNextHashState 都會向『前』移動一次:

int NXNextHashState (NXHashTable *table, NXHashState *state, void **data) {
    HashBucket        *buckets = (HashBucket *) table->buckets;

    while (state->j == 0) {
        if (state->i == 0) return NO;
        state->i--; state->j = buckets[state->i].count;
    }
    state->j--;
    buckets += state->i;
    *data = (void *) ((buckets->count == 1)
                      ? buckets->elements.one : buckets->elements.many[state->j]);
    return YES;
};

下面的 gif 爲我麼麼展現了每一次調用 NXNextHashState 方法以後當前的 NXHashState

objc-hashtable-hashstate-next

NXHashRemove

這裏的 NXHashRemove在某種意義上是 NXHashInsert 的逆操做:

void *NXHashRemove (NXHashTable *table, const void *data) {
    HashBucket    *bucket = BUCKETOF(table, data);
    unsigned    j = bucket->count;
    const void    **pairs;
    const void    **newt;
    __unused void *z = ZONE_FROM_PTR(table);

    if (! j) return NULL;
    if (j == 1) {
        if (! ISEQUAL(table, data, bucket->elements.one)) return NULL;
        data = bucket->elements.one;
        table->count--; bucket->count--; bucket->elements.one = NULL;
        return (void *) data;
    };
    pairs = bucket->elements.many;
    if (j == 2) {
        if (ISEQUAL(table, data, pairs[0])) {
            bucket->elements.one = pairs[1]; data = pairs[0];
        }
        else if (ISEQUAL(table, data, pairs[1])) {
            bucket->elements.one = pairs[0]; data = pairs[1];
        }
        else return NULL;
        FREEPAIRS (pairs);
        table->count--; bucket->count--;
        return (void *) data;
    };
    while (j--) {
        if (ISEQUAL(table, data, *pairs)) {
            data = *pairs;
            /* we shrink this bucket */
            newt = (bucket->count-1)
            ? ALLOCPAIRS(z, bucket->count-1) : NULL;
            if (bucket->count-1 != j)
                bcopy ((const char*)bucket->elements.many, (char*)newt, PTRSIZE*(bucket->count-j-1));
            if (j)
                bcopy ((const char*)(bucket->elements.many + bucket->count-j), (char*)(newt+bucket->count-j-1), PTRSIZE*j);
            FREEPAIRS (bucket->elements.many);
            table->count--; bucket->count--; bucket->elements.many = newt;
            return (void *) data;
        };
        pairs ++;
    };
    return NULL;
}

它的實現也分爲三種狀況,不過在這裏就很少說了。

NXHashTable 的性能

在已經熟悉了 NXHashTable 的具體實現以後,咱們要分析插入不一樣數據量級的狀況下,所須要的時間,這裏是主程序的代碼,分別測試了在 100, 1000, 10000, 100000, 1000000, 2000000, 3000000, 5000000, 10000000 數據下 NXHashTable 的性能表現:

#import <Foundation/Foundation.h>
#import "hashtable2.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray<NSNumber *> *capacities = @[
            @100,
            @1000,
            @10000,
            @100000,
            @1000000,
            @2000000,
            @3000000,
            @5000000,
            @10000000
        ];

        for (NSNumber *capacity in capacities) {
            NXHashTable *hashTable = NXCreateHashTable(NXPtrPrototype, 0, NULL);
            NSDate *methodStart = [NSDate date];
            for (NSInteger i = 0; i < capacity.integerValue; i++) {
                NSString *value = [NSString stringWithFormat:@"%ld", (long)i];
                NXHashInsert(hashTable, (__bridge void *)value);
            }
            NSDate *methodFinish = [NSDate date];
            NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
            NSLog(@"Capacities: %@, executionTime = %f, meanTime = %.10f", capacity, executionTime, executionTime / capacity.integerValue);

            free(hashTable);
        }

    }
    return 0;
}

代碼中初始化了一個 capacities 存儲須要測量的數據量級,而後調用 NXHashInsert 方法將至關數量級的數據添加到哈希表中:

Capacities Execution Time Mean Time
100 0.000334 0.0000033402
1000 0.001962 0.0000019619
10000 0.022001 0.0000022001
100000 0.349998 0.0000035000
1000000 2.622551 0.0000026226
2000000 4.165023 0.0000020825
3000000 6.973098 0.0000023244
5000000 13.179743 0.0000026359
10000000 53.387356 0.0000053387

在對 NXHashTable 的性能測試中,當數據量小於 5000000 時,執行時間的增加仍是線性的,平均時間也基本穩定,可是一旦數據量達到了千萬級,執行時間就會出現顯著的增加。

若是僅僅在哈希表中插入數據,相信其時間增加應該都是線性的,這裏出現問題的緣由推測是在對哈希表進行 Rehash 的時候,遷移原數據至新的數組所形成的

如何避免哈希表的 Rehash 呢,從新回顧一下建立哈希表的函數:

NXHashTable *NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void *info);

這個函數的簽名中包含一個 capacity 的參數,咱們在上面的代碼中傳入了 0,也就是最開始的 buckets 數爲 0,可是它的數目並非固定的,它會隨着哈希表中數據的增多,逐漸變大。

capacity 只是一個提示,幫助 NXHashTable 瞭解其中會存儲多少數據。

若是在建立 NXHashTable 時傳入 capacity.integerValue

NXHashTable *hashTable = NXCreateHashTable(NXPtrPrototype, capacity.integerValue, NULL);

從新運行代碼,測量性能:

Capacities Execution Time Mean Time
100 0.000740 0.0000073999
1000 0.003442 0.0000034420
10000 0.023341 0.0000023341
100000 0.215209 0.0000021521
1000000 1.836802 0.0000018368
2000000 3.683246 0.0000018416
3000000 5.474610 0.0000018249
5000000 10.576254 0.0000021153
10000000 46.725459 0.0000046725

雖然在測試 10,000,000 數據時其平均時間依然是 5,000,000 時的二倍,不過總體的性能都有所提高,然而這部分性能的損耗暫時還不是很清楚緣由。

若是咱們使用 Instrument 對有無 capacity 的狀況進行比較(這是在使用 2,000,000 數據時進行的測試):

objc-hashtable-instrument

沒有傳入 capacity 的哈希表會在屢次插入以後出現一個峯值(因爲 Rehash 引發的,其寬度就是 Rehash 使用的時間),而傳入 capacity 的哈希表會在代碼剛運行時就初始化足夠大的數組。

NSMutableArray 性能

這部分只算是一個小插曲,你能夠選擇跳過這一小節的內容。

NSMutableArray 的構造器 - (instancetype)initWithCapacity:(NSUInteger)numItems 也有一個參數 capacity,雖然數組和哈希表是兩種數據結構。

不過咱們這裏主要研究的是:傳入 capacity 是否會對性能形成影響

首先是使用 init 建立的 NSMutableArray 數組,也就是沒有傳入 capacity

Capacities Execution Time Mean Time
100 0.000539 0.0000053900
1000 0.003185 0.0000031850
10000 0.074033 0.0000074033
100000 0.370899 0.0000037090
1000000 1.504855 0.0000015049
2000000 2.852519 0.0000014263
3000000 3.995536 0.0000013318
5000000 6.833879 0.0000013668
10000000 14.444605 0.0000014445

下面是使用 initWithCapacity: 建立的數組:

Capacities Execution Time Mean Time
100 0.000256 0.0000025600
1000 0.001775 0.0000017750
10000 0.015906 0.0000015906
100000 0.174376 0.0000017438
1000000 1.650481 0.0000016505
2000000 2.802310 0.0000014012
3000000 4.451261 0.0000014838
5000000 7.093753 0.0000014188
10000000 14.598415 0.0000014598

你能夠在表格中看到,二者在執行效率上並無顯著的差別或者區別。

可是若是使用 instrument 來查看二者的內存分配,能夠很明顯的看到,沒有傳入 capacityNSMutableArray 會在可變數組內存佔用增長前出現一個短暫的內存分配峯值

objc-hashtable-nsarray-instrument

致使這一現象的原始多是:在將原數組中的內容移入新數組時,臨時變量申請了大量的內存控件

在以後關於 CoreFoundation 源代碼分析的文中會介紹它們是怎麼實現的。

NXHashTable 的應用

在整個 objc/runtime 中,做爲私有的數據結構 NXHashTable,直接使用了它的就是存儲全部類或者元類的哈希表(在這裏會忽略對元類的存儲,由於實現幾乎徹底相同):

static NXHashTable *realized_class_hash = nil;

我麼可使用 objc_copyClassList 獲取類的數組:

Class *
objc_copyClassList(unsigned int *outCount)
{
    rwlock_writer_t lock(runtimeLock);

    realizeAllClasses();

    Class *result = nil;
    NXHashTable *classes = realizedClasses();
    unsigned int count = NXCountHashTable(classes);

    if (count > 0) {
        Class cls;
        NXHashState state = NXInitHashState(classes);
        result = (Class *)malloc((1+count) * sizeof(Class));
        count = 0;
        while (NXNextHashState(classes, &state, (void **)&cls)) {
            result[count++] = cls;
        }
        result[count] = nil;
    }
        
    if (outCount) *outCount = count;
    return result;
}
  1. 調用 realizedClasses 返回 realized_class_hash 哈希表

  2. 使用 NSHashState 遍歷 realized_class_hash 中的類,並將全部的類存入 result

接下來使用上面的方法,打印出 realized_class_hash 中存儲的全部類:

objc-hashtable-copy-class-list

小結

NXHashTable 在 OS X 10.1 中就已經標記爲棄用了,可是依舊支持着 runtime 底層的工做。

NXHashTable 能夠說有着很是很是久遠的歷史了,最先能夠追溯到將近 30 多年前 NeXT 時代:

// hashtable2.mm 文件中

hashtable2.m
Copyright 1989-1996 NeXT Software, Inc.
Created by Bertrand Serlet, Feb 89

NSHashTable 對哈希表的實現仍是很是優雅的,能夠說很是標準的使用了拉鍊法實現哈希表。

不過如今,咱們會使用 NSHashTable 來取代這個上古時代的產物。

關注倉庫,及時得到更新:iOS-Source-Code-Analyze
Follow: Draveness · Github

相關文章
相關標籤/搜索