探索 NSDictionary

Exposing NSDictinoary

The Class

相当多的类都是 class clusters,当然 NSDictionary 也不例外,曾经一段时间 NSDictinoary 使用了 CFDictionary 作为它的默认实现。然而,在 iOS 6以后事情改变了。。。

1
2
(lldb) po [[NSDictionary new] class]
__NSDictionaryI

和 __NSArrayM 一样, __NSDictionaryI 也是在 CoreFoundation Framework 中:

1
2
3
4
5
@interface __NSDictionaryI : NSDictionary
{
NSUIngeter _used:58;
NSUIngeter _szidx:6;
}

The Storage

Instance Creation

想要理解 __NSDictionaryI 在哪里保存内容,我们来看看对象创建的过程,只有一个类方法负责创建实例:__NSDictionaryI。根据 class-dump 的输出,方法有如下的签名:

1
+ (id)__new:(const id *)arg1:(const id *)arg2:(unsigned long long)arg3:(_Bool)arg4:(_Bool)arg5;

他需要5各参数,只有第一个是指名字,如果我们使用@selector 的方式来写的话,应该是这样 @selector(__new:::::)。前三个参数通过设置断点,然后看一下寄存器 x2、x3、x4的内容,看到他们是分别是:array 的 key、array 的 object、key(object) 的个数。注意和对外公布的 api 相比,参数的位置是互换的。

1
+ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt;

其实参数定义为const id *或者const id []没有太大的关系。剩下的就是两个BOOL 类型的参数。第四个参数是指 key 是否需要被拷贝。第五个参数决定了参数是否不需要被 retain。我们可以重写该函数:

1
+ (id)__new:(const id *)keys :(const id *)objects :(unsigned long long)count :(_Bool)copyKeys :(_Bool)dontRetain;

Indexed ivars

除了函数+ __new:::::以外,malloccalloc 并没有地方被调用。实际上,函数调用了 __CFAllocateObject2 方法,并且传递了 __NSDictionaryI 作为第一个参数,然后用需要的大小作为第二个参数。__CFAllocateObject2 实际上调用了 class_createInstance ,然后把同样的参数传给他。

class_createInstance(Class cls, size_t extraBytes) 调用了 _class_createInstanceFromZone 并且传个 nil 作为 zone 的参数。

1
2
3
4
5
6
7
8
9
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
...
size_t size = cls->alignedInstanceSize() + extraBytes;
...
id obj = (id)calloc(1, size);
...
return obj;
}

extreBytes 参数,calloc 函数调用确保了所有的 ivar 都是 0.

indexed ivars就是在普通的 ivar 后面

剩下的就是分配内存了

1
void *object_getIndexedIvars(id obj)

关于 indexed ivar 有一些比较好玩的事情。

  • 每一个实例都可以有不同数量的 extraBytes指向他
  • 存取速度很快。
  • 可以对 class-dump 这样的工具隐藏实现细节。

但是:在 arc 环境下是不能直接编译 class_createInstance,需要增加 -fno-objc-arc 标志位。。运行时没有保存 indexed ivars 信息。

key & 访问一个对象

C Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (id)objectForKey:(id)aKey
{
NSUInteger sizeIndex = _szidx;
NSUInteger size = __NSDictionarySizes[sizeIndex];

id *storage = (id *)object_getIndexedIvars(dict);

NSUInteger fetchIndex = [aKey hash] % size;

for (int i = 0; i < size; i++) {
id fetchedKey = storage[2 * fetchIndex];

if (fetchedKey == nil) {
return nil;
}

if (fetchedKey == aKey || [fetchedKey isEqual:aKey]) {
return storage[2 * fetchIndex + 1];
}

fetchIndex++;

if (fetchIndex == size) {
fetchIndex = 0;
}
}

return nil;
}

上述代码是从汇编反推出来的。

作者

shouyi.www

发布于

2019-12-04

更新于

2025-01-30

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×