MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C集合类(NSArray、NSDictionary)底层原理

2022-02-111.7k 阅读

NSArray 底层原理

NSArray 概述

在Objective-C编程中,NSArray是一种非常常用的集合类,用于存储和管理一组有序的对象。它提供了方便的方法来访问、遍历和操作数组中的元素。NSArray是不可变的,这意味着一旦创建,其内容就不能被修改。如果需要可变的数组,可以使用NSMutableArray,它是NSArray的子类,继承了NSArray的特性并添加了修改数组内容的方法。

NSArray 的内存结构

NSArray的底层实现是基于C语言数组的。在64位系统中,NSArray对象占用16字节,其中8字节用于存储对象的指针(指向类对象的元数据),另外8字节存储数组的一些内部信息,例如数组元素的个数等。

数组中的每个元素都是对象指针,这意味着NSArray只能存储对象类型,不能存储基本数据类型(如intfloat等)。如果要存储基本数据类型,需要将其包装成对象,例如使用NSNumber类。

NSArray 的创建和初始化

NSArray有多种创建和初始化的方式,下面是一些常见的示例:

// 空数组
NSArray *emptyArray = [NSArray array];

// 通过字面量创建数组
NSArray *arrayWithLiterals = @[@"one", @"two", @"three"];

// 通过构造方法创建数组
NSArray *arrayWithConstructors = [NSArray arrayWithObjects:@"one", @"two", @"three", nil];

在通过arrayWithObjects: nil方法创建数组时,需要以nil作为参数列表的结束标志,这是因为该方法内部是通过遍历参数列表直到遇到nil来确定数组元素的个数的。

NSArray 的访问和遍历

访问NSArray中的元素非常简单,可以通过索引来获取指定位置的元素:

NSArray *array = @[@"one", @"two", @"three"];
NSString *element = array[1]; // 获取索引为1的元素,即@"two"

遍历NSArray也有多种方式,常见的有for - in循环和enumerateObjectsUsingBlock:方法:

// 使用for - in循环遍历
NSArray *array = @[@"one", @"two", @"three"];
for (NSString *element in array) {
    NSLog(@"%@", element);
}

// 使用enumerateObjectsUsingBlock:方法遍历
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Index %lu: %@", (unsigned long)idx, obj);
    if (idx == 1) {
        *stop = YES; // 停止遍历
    }
}];

for - in循环遍历简单直观,适用于大多数情况。而enumerateObjectsUsingBlock:方法可以在遍历过程中获取元素的索引,并且可以通过stop指针来控制是否停止遍历,适用于需要更复杂逻辑的场景。

NSArray 的内存管理

由于NSArray中的元素都是对象指针,所以内存管理遵循Objective-C的引用计数机制。当一个对象被添加到NSArray中时,其引用计数会增加;当对象从NSArray中移除(例如数组被释放)时,对象的引用计数会减少。

NSObject *obj = [[NSObject alloc] init];
NSArray *array = @[obj];
// obj的引用计数在加入数组时增加
[array release];
// 数组释放后,obj的引用计数减少
[obj release];
// obj的引用计数再次减少,对象被释放

这种内存管理方式确保了数组中的对象在数组存在期间不会被意外释放,同时在数组释放时,数组中的对象也能得到正确的释放。

NSMutableArray 底层原理

NSMutableArray 概述

NSMutableArrayNSArray的可变子类,它允许在数组创建后动态地添加、删除和修改元素。这使得NSMutableArray在需要频繁改变数据结构的场景中非常有用,例如在处理用户输入数据或者实时更新的数据集合时。

NSMutableArray 的内存结构

NSMutableArrayNSArray的基础上增加了一些用于管理动态变化的结构。除了继承NSArray的16字节结构外,NSMutableArray还需要额外的内存来存储当前数组的容量(即当前分配的可容纳元素的数量)以及实际存储的元素个数。

NSMutableArray内部的存储结构通常是一个动态增长的数组。当添加元素时,如果当前容量不足,会重新分配内存,扩大数组的容量,通常会以一定的倍数(如2倍)增长,以减少内存重新分配的频率。

NSMutableArray 的创建和初始化

NSMutableArray同样有多种创建和初始化的方式:

// 创建一个空的可变数组
NSMutableArray *mutableArray = [NSMutableArray array];

// 创建一个具有初始容量的可变数组
NSMutableArray *mutableArrayWithCapacity = [NSMutableArray arrayWithCapacity:5];

// 通过现有数组创建可变数组
NSArray *array = @[@"one", @"two", @"three"];
NSMutableArray *mutableArrayFromArray = [NSMutableArray arrayWithArray:array];

通过arrayWithCapacity:方法创建可变数组时,可以指定初始容量,这样可以在一定程度上优化内存分配,避免频繁的扩容操作。

NSMutableArray 的操作方法

NSMutableArray提供了丰富的方法来操作数组内容,例如添加元素:

NSMutableArray *mutableArray = [NSMutableArray array];
[mutableArray addObject:@"one"];
[mutableArray addObject:@"two"];

删除元素:

[mutableArray removeObject:@"two"];
[mutableArray removeObjectAtIndex:0];

修改元素:

[mutableArray replaceObjectAtIndex:0 withObject:@"newValue"];

这些操作方法在实现时,需要考虑到数组容量的变化以及元素的移动等问题。例如,当删除元素时,需要将后面的元素向前移动,以保持数组的连续性。

NSMutableArray 的性能优化

由于NSMutableArray的动态特性,频繁的添加、删除操作可能会导致性能问题,主要原因是内存的重新分配和元素的移动。为了优化性能,可以在初始化时尽量预估数组的大小,使用arrayWithCapacity:方法设置合适的初始容量,减少扩容操作的次数。

另外,在进行大量的添加或删除操作时,可以考虑使用beginUpdatesendUpdates方法,这两个方法可以将多个操作合并为一次更新,减少中间的数组调整操作,提高性能。

[mutableArray beginUpdates];
for (int i = 0; i < 100; i++) {
    [mutableArray addObject:[NSString stringWithFormat:@"Object %d", i]];
}
[mutableArray endUpdates];

NSDictionary 底层原理

NSDictionary 概述

NSDictionary是Objective-C中用于存储键值对的集合类。它提供了一种快速查找和访问数据的方式,通过唯一的键来关联对应的值。NSDictionary是不可变的,一旦创建,其内容不能被修改。如果需要可变的字典,可以使用NSMutableDictionary

NSDictionary 的内存结构

NSDictionary的底层实现通常基于哈希表(Hash Table)。哈希表是一种数据结构,通过哈希函数将键映射到一个哈希值,然后根据哈希值确定键值对在表中的存储位置。这样可以实现快速的查找操作,理论上查找的时间复杂度接近O(1)。

在64位系统中,NSDictionary对象本身占用16字节,同样包含对象指针和一些内部信息。哈希表中的每个槽位(bucket)存储一个键值对,键和值都是对象指针。

NSDictionary 的创建和初始化

NSDictionary有多种创建和初始化方式:

// 空字典
NSDictionary *emptyDictionary = [NSDictionary dictionary];

// 通过字面量创建字典
NSDictionary *dictionaryWithLiterals = @{@"key1": @"value1", @"key2": @"value2"};

// 通过构造方法创建字典
NSDictionary *dictionaryWithConstructors = [NSDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil];

使用字面量创建字典是最常见和简洁的方式,而dictionaryWithObjectsAndKeys: nil方法需要以nil作为参数列表的结束标志。

NSDictionary 的访问和遍历

通过键来访问NSDictionary中的值非常简单:

NSDictionary *dictionary = @{@"key1": @"value1", @"key2": @"value2"};
NSString *value = dictionary[@"key1"];

遍历NSDictionary可以使用for - in循环遍历键,然后通过键获取值:

for (NSString *key in dictionary) {
    NSString *value = dictionary[key];
    NSLog(@"Key: %@, Value: %@", key, value);
}

也可以使用enumerateKeysAndObjectsUsingBlock:方法进行遍历:

[dictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
    NSLog(@"Key: %@, Value: %@", key, obj);
    if ([key isEqualToString:@"key1"]) {
        *stop = YES;
    }
}];

这种方式可以在遍历过程中获取键值对,并通过stop指针控制遍历的停止。

NSDictionary 的哈希和内存管理

NSDictionary使用哈希表来存储键值对,因此键对象需要实现hash方法和isEqual:方法。hash方法用于生成键的哈希值,isEqual:方法用于比较两个键是否相等。

在内存管理方面,当一个键值对被添加到NSDictionary中时,键和值的引用计数都会增加。当字典被释放时,键和值的引用计数会减少,从而确保对象的正确释放。

NSMutableDictionary 底层原理

NSMutableDictionary 概述

NSMutableDictionaryNSDictionary的可变子类,允许在字典创建后动态地添加、删除和修改键值对。这使得它在需要实时更新数据的场景中非常有用,例如在网络请求过程中动态更新缓存数据。

NSMutableDictionary 的内存结构

NSMutableDictionaryNSDictionary基于哈希表的结构基础上,增加了一些用于管理动态变化的机制。它需要额外的内存来记录当前哈希表的容量、实际存储的键值对数量以及一些用于处理哈希冲突的信息。

当添加键值对时,如果当前哈希表的负载因子(实际存储的键值对数量与哈希表容量的比值)超过一定阈值(通常为0.75),会触发哈希表的扩容操作,重新分配内存并重新计算键的哈希值和存储位置。

NSMutableDictionary 的创建和初始化

NSMutableDictionary的创建和初始化方式如下:

// 创建一个空的可变字典
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];

// 创建一个具有初始容量的可变字典
NSMutableDictionary *mutableDictionaryWithCapacity = [NSMutableDictionary dictionaryWithCapacity:5];

// 通过现有字典创建可变字典
NSDictionary *dictionary = @{@"key1": @"value1", @"key2": @"value2"};
NSMutableDictionary *mutableDictionaryFromDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary];

通过dictionaryWithCapacity:方法可以指定初始容量,有助于减少扩容操作的频率。

NSMutableDictionary 的操作方法

NSMutableDictionary提供了丰富的方法来操作键值对,例如添加键值对:

NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
[mutableDictionary setObject:@"value1" forKey:@"key1"];

删除键值对:

[mutableDictionary removeObjectForKey:@"key1"];

修改键值对:

[mutableDictionary setObject:@"newValue" forKey:@"key1"];

在实现这些操作方法时,需要处理哈希表的扩容、键值对的插入和删除等复杂逻辑。例如,在删除键值对时,需要根据键的哈希值找到对应的存储位置,并处理可能的哈希冲突。

NSMutableDictionary 的性能优化

NSMutableDictionary的性能优化主要围绕减少哈希冲突和合理控制哈希表的扩容。可以在初始化时根据预估的数据量设置合适的初始容量,避免频繁的扩容操作。

另外,在设计键对象时,尽量使键的hash方法生成的哈希值分布均匀,减少哈希冲突的发生。可以通过合理选择哈希算法或者对键进行预处理来实现。

在进行大量的添加或删除操作时,可以考虑使用beginUpdatesendUpdates方法,将多个操作合并为一次更新,提高性能。

[mutableDictionary beginUpdates];
for (int i = 0; i < 100; i++) {
    [mutableDictionary setObject:[NSString stringWithFormat:@"Value %d", i] forKey:[NSString stringWithFormat:@"Key %d", i]];
}
[mutableDictionary endUpdates];

通过深入了解NSArrayNSMutableArrayNSDictionaryNSMutableDictionary的底层原理,开发者可以在实际编程中更加合理地使用这些集合类,优化程序的性能和内存使用。无论是在小型应用还是大型项目中,对集合类底层原理的掌握都能帮助开发者写出更高效、稳定的代码。