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

探秘Objective-C中可变数组的特性与应用

2021-04-241.2k 阅读

可变数组的基本概念

在Objective - C编程中,NSMutableArray 是一种非常重要的数据结构,它代表了可变数组。与不可变数组 NSArray 不同,NSMutableArray 允许我们在运行时动态地添加、删除和修改数组中的元素。

NSMutableArray 继承自 NSArray,这意味着它拥有 NSArray 的所有特性,例如可以通过索引访问元素,并且遵循 NSFastEnumeration 协议以支持快速枚举。但同时,NSMutableArray 扩展了 NSArray 的功能,使其具备了数组内容的可变性。

可变数组的创建

创建一个 NSMutableArray 实例有多种方式。最常见的是使用类方法 arrayarrayWithCapacity:array 方法创建一个空的可变数组,而 arrayWithCapacity: 方法则可以预先指定数组的初始容量,这样在添加元素时可以减少内存重新分配的次数,提高性能。

下面是创建 NSMutableArray 的代码示例:

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

// 创建一个初始容量为10的可变数组
NSMutableArray *arrayWithCapacity = [NSMutableArray arrayWithCapacity:10];

还可以从已有的数组创建 NSMutableArray,例如:

NSArray *immutableArray = @[@"one", @"two", @"three"];
NSMutableArray *mutableArrayFromExisting = [NSMutableArray arrayWithArray:immutableArray];

上述代码将不可变数组 immutableArray 的内容复制到新创建的可变数组 mutableArrayFromExisting 中。

可变数组的元素操作

添加元素

  1. 添加单个元素 NSMutableArray 提供了 addObject: 方法用于向数组末尾添加一个对象。例如:
NSMutableArray *shoppingList = [NSMutableArray array];
[shoppingList addObject:@"Apple"];
[shoppingList addObject:@"Banana"];

在上述代码中,shoppingList 数组最初为空,通过 addObject: 方法依次添加了 “Apple” 和 “Banana” 两个字符串对象。

  1. 在指定位置添加元素 如果需要在数组的特定位置插入元素,可以使用 insertObject:atIndex: 方法。例如:
NSMutableArray *numbers = [NSMutableArray arrayWithArray:@[@1, @3, @4]];
[numbers insertObject:@2 atIndex:1];

上述代码在 numbers 数组的索引为1的位置插入了对象 @2,插入后数组变为 [@1, @2, @3, @4]

  1. 添加多个元素 若要一次性添加多个元素,可以使用 addObjectsFromArray: 方法。例如:
NSMutableArray *firstArray = [NSMutableArray arrayWithArray:@[@1, @2]];
NSArray *secondArray = @[@3, @4];
[firstArray addObjectsFromArray:secondArray];

执行上述代码后,firstArray 包含 [@1, @2, @3, @4],即将 secondArray 的所有元素添加到了 firstArray 的末尾。

删除元素

  1. 删除最后一个元素 NSMutableArray 提供了 removeLastObject 方法来删除数组中的最后一个元素。例如:
NSMutableArray *colors = [NSMutableArray arrayWithArray:@[@"Red", @"Green", @"Blue"]];
[colors removeLastObject];

执行上述代码后,colors 数组变为 [@"Red", @"Green"],“Blue” 被从数组中移除。

  1. 删除指定位置的元素 removeObjectAtIndex: 方法用于删除指定索引位置的元素。例如:
NSMutableArray *fruits = [NSMutableArray arrayWithArray:@[@"Apple", @"Banana", @"Cherry"]];
[fruits removeObjectAtIndex:1];

上述代码删除了 fruits 数组中索引为1的元素 “Banana”,数组变为 [@"Apple", @"Cherry"]

  1. 删除特定对象 若要删除数组中的特定对象,可以使用 removeObject: 方法。例如:
NSMutableArray *animals = [NSMutableArray arrayWithArray:@[@"Dog", @"Cat", @"Bird"]];
[animals removeObject:@"Cat"];

执行上述代码后,“Cat” 从 animals 数组中被移除,数组变为 [@"Dog", @"Bird"]

如果数组中存在多个相同的对象,removeObject: 方法只会移除第一个匹配的对象。若要移除所有匹配的对象,可以使用 removeObjectIdenticalTo: 方法。例如:

NSMutableArray *duplicateNumbers = [NSMutableArray arrayWithArray:@[@1, @2, @2, @3]];
[duplicateNumbers removeObjectIdenticalTo:@2];

执行上述代码后,duplicateNumbers 数组变为 [@1, @3],所有值为 @2 的对象都被移除。

修改元素

修改 NSMutableArray 中的元素相对简单,通过索引访问元素并赋予新的值即可。例如:

NSMutableArray *scores = [NSMutableArray arrayWithArray:@[@80, @90, @75]];
scores[1] = @95;

上述代码将 scores 数组中索引为1的元素从 @90 修改为 @95

可变数组的内存管理与性能优化

内存管理

在Objective - C中,NSMutableArray 遵循引用计数的内存管理原则。当向数组中添加一个对象时,数组会对该对象发送 retain 消息,增加其引用计数。当从数组中移除对象或数组本身被销毁时,数组会对这些对象发送 release 消息,减少其引用计数。

例如,假设我们有一个自定义的类 MyObject

@interface MyObject : NSObject
@end

@implementation MyObject
- (void)dealloc {
    NSLog(@"MyObject deallocated");
    [super dealloc];
}
@end

然后创建一个 NSMutableArray 并添加 MyObject 的实例:

NSMutableArray *objectArray = [NSMutableArray array];
MyObject *obj = [[MyObject alloc] init];
[objectArray addObject:obj];
[obj release];

在上述代码中,obj 的引用计数最初为1,通过 addObject: 方法添加到数组后,数组对 obj 进行 retain,引用计数变为2,然后我们手动发送 release 消息,引用计数又变回1。当 objectArray 被销毁或 obj 从数组中移除时,数组会对 obj 发送 release 消息,引用计数变为0,objdealloc 方法会被调用,对象内存被释放。

性能优化

  1. 预分配容量 如前文所述,使用 arrayWithCapacity: 方法创建 NSMutableArray 时,可以预先指定数组的容量。这对于性能优化非常重要,因为如果不预先分配容量,每次添加元素导致数组容量不足时,系统需要重新分配内存,复制原有元素到新的内存地址,这会带来额外的性能开销。

例如,假设我们要向数组中添加1000个元素:

// 未预分配容量
NSMutableArray *noCapacityArray = [NSMutableArray array];
for (NSInteger i = 0; i < 1000; i++) {
    [noCapacityArray addObject:@(i)];
}

// 预分配容量
NSMutableArray *capacityArray = [NSMutableArray arrayWithCapacity:1000];
for (NSInteger i = 0; i < 1000; i++) {
    [capacityArray addObject:@(i)];
}

在实际应用中,预分配容量的 capacityArray 在添加元素时性能会优于 noCapacityArray,因为它减少了内存重新分配的次数。

  1. 批量操作优于单个操作 在对数组进行操作时,尽量使用批量操作方法,如 addObjectsFromArray:removeObjectsInArray:,而不是多次调用单个添加或删除方法。这是因为批量操作通常在底层实现上更加高效,减少了方法调用的开销。

例如,要添加多个元素到数组中:

NSArray *toAdd = @[@1, @2, @3];
NSMutableArray *targetArray = [NSMutableArray array];

// 单个添加操作
for (NSNumber *number in toAdd) {
    [targetArray addObject:number];
}

// 批量添加操作
[targetArray addObjectsFromArray:toAdd];

批量添加操作 addObjectsFromArray: 的性能会优于逐个添加操作的循环。

可变数组与其他数据结构的交互

与不可变数组的转换

NSMutableArray 可以很方便地转换为 NSArray,只需要使用 NSArray 的类方法 arrayWithArray: 即可。例如:

NSMutableArray *mutableData = [NSMutableArray arrayWithArray:@[@"A", @"B", @"C"]];
NSArray *immutableData = [NSArray arrayWithArray:mutableData];

上述代码将 mutableData 可变数组转换为了不可变数组 immutableData

反过来,NSArray 也可以转换为 NSMutableArray,使用 NSMutableArray 的类方法 arrayWithArray:。例如:

NSArray *originalArray = @[@1, @2, @3];
NSMutableArray *mutableCopy = [NSMutableArray arrayWithArray:originalArray];

这样就从不可变数组 originalArray 创建了一个可变数组 mutableCopy

与集合的交互

NSMutableArray 与集合类(如 NSMutableSet)之间也可以进行交互。例如,可以将 NSMutableArray 的元素添加到 NSMutableSet 中,或者从 NSMutableSet 中获取元素填充到 NSMutableArray 中。

NSMutableArray 的元素添加到 NSMutableSet

NSMutableArray *arrayOfNumbers = [NSMutableArray arrayWithArray:@[@1, @2, @2, @3]];
NSMutableSet *numberSet = [NSMutableSet set];
[numberSet addObjectsFromArray:arrayOfNumbers];

上述代码将 arrayOfNumbers 数组中的元素添加到了 numberSet 集合中,由于集合的特性,重复的元素 “2” 只会在集合中保留一个。

NSMutableSet 获取元素填充到 NSMutableArray

NSMutableSet *colorSet = [NSMutableSet setWithArray:@[@"Red", @"Green", @"Blue"]];
NSMutableArray *colorArray = [NSMutableArray arrayWithArray:colorSet.allObjects];

这里通过 NSMutableSetallObjects 属性获取集合中的所有对象,并创建了一个 NSMutableArray

可变数组在实际项目中的应用场景

数据缓存

在应用开发中,经常需要缓存一些数据,NSMutableArray 可以作为一种简单的数据缓存结构。例如,在一个新闻应用中,可以将获取到的新闻文章对象缓存到 NSMutableArray 中,当用户再次请求相同数据时,可以直接从缓存数组中获取,而不需要再次从网络请求。

@interface NewsArticle : NSObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *content;
@end

@implementation NewsArticle
@end

// 模拟新闻数据缓存
NSMutableArray *newsCache = [NSMutableArray array];

// 假设从网络获取新闻文章
NewsArticle *article1 = [[NewsArticle alloc] init];
article1.title = @"First News";
article1.content = @"This is the first news content.";
[newsCache addObject:article1];
[article1 release];

// 下次请求新闻数据时,先检查缓存
for (NewsArticle *article in newsCache) {
    if ([article.title isEqualToString:@"First News"]) {
        // 使用缓存中的新闻数据
        NSLog(@"Using cached news: %@", article.content);
        break;
    }
}

列表展示

在iOS应用的界面开发中,UITableViewUICollectionView 等视图通常需要一个数据源数组来展示数据。NSMutableArray 非常适合作为数据源,因为它可以动态地更新数据,从而实现列表的动态刷新。

例如,在一个待办事项应用中,用户可以添加新的待办事项,删除已完成的事项,这些操作都可以通过对 NSMutableArray 进行相应的添加和删除元素操作来实现,然后通知 UITableView 刷新界面。

@interface TodoItem : NSObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assign) BOOL isCompleted;
@end

@implementation TodoItem
@end

// 待办事项数据源数组
NSMutableArray *todoList = [NSMutableArray array];

// 添加新的待办事项
TodoItem *newTodo = [[TodoItem alloc] init];
newTodo.title = @"Buy groceries";
newTodo.isCompleted = NO;
[todoList addObject:newTodo];
[newTodo release];

// 假设用户完成了一个待办事项并删除
for (TodoItem *item in todoList) {
    if ([item.title isEqualToString:@"Buy groceries"]) {
        [todoList removeObject:item];
        break;
    }
}

// 通知UITableView刷新
// 假设self.tableView是一个UITableView实例
[self.tableView reloadData];

游戏开发中的应用

在游戏开发中,NSMutableArray 可以用于管理游戏中的各种对象,比如游戏角色、道具等。例如,在一个角色扮演游戏中,玩家可以收集各种道具,这些道具可以存储在 NSMutableArray 中。玩家使用道具时,可以从数组中移除相应的道具对象;玩家获得新道具时,可以添加到数组中。

@interface GameItem : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger value;
@end

@implementation GameItem
@end

// 玩家的道具数组
NSMutableArray *playerItems = [NSMutableArray array];

// 玩家获得一个新道具
GameItem *newItem = [[GameItem alloc] init];
newItem.name = @"Health Potion";
newItem.value = 50;
[playerItems addObject:newItem];
[newItem release];

// 玩家使用一个道具
for (GameItem *item in playerItems) {
    if ([item.name isEqualToString:@"Health Potion"]) {
        [playerItems removeObject:item];
        break;
    }
}

可变数组使用中的常见问题与解决方法

数组越界问题

在使用 NSMutableArray 时,最常见的问题之一就是数组越界。当通过索引访问或修改数组元素时,如果索引超出了数组的有效范围,就会导致程序崩溃。

例如:

NSMutableArray *smallArray = [NSMutableArray arrayWithArray:@[@1, @2]];
// 以下代码会导致数组越界错误
NSNumber *outOfBoundsNumber = smallArray[10];

上述代码尝试访问索引为10的元素,但 smallArray 只有两个元素,索引范围是0到1,因此会引发错误。

解决方法是在访问或修改元素之前,先检查索引是否在有效范围内。可以使用 count 属性获取数组的元素个数,然后进行判断。例如:

NSMutableArray *smallArray = [NSMutableArray arrayWithArray:@[@1, @2]];
NSUInteger index = 10;
if (index < smallArray.count) {
    NSNumber *number = smallArray[index];
} else {
    NSLog(@"Index out of bounds");
}

类型不匹配问题

NSMutableArray 可以存储任何类型的对象,但在实际使用中,通常会期望数组中存储特定类型的对象。如果不小心将错误类型的对象添加到数组中,可能会在后续操作中导致难以调试的错误。

例如,假设我们期望一个数组只存储 NSNumber 对象,但不小心添加了一个 NSString 对象:

NSMutableArray *numberArray = [NSMutableArray array];
[numberArray addObject:@1];
// 错误地添加了一个NSString对象
[numberArray addObject:@"two"];

// 假设后续代码期望数组中的元素都是NSNumber类型并进行数学运算
for (NSNumber *number in numberArray) {
    NSLog(@"%f", number.floatValue);
    // 当遇到NSString对象时,会导致崩溃,因为NSString没有floatValue属性
}

解决方法是在添加对象到数组之前进行类型检查。可以使用 isKindOfClass: 方法进行判断。例如:

NSMutableArray *numberArray = [NSMutableArray array];
NSObject *obj1 = @1;
NSObject *obj2 = @"two";

if ([obj1 isKindOfClass:[NSNumber class]]) {
    [numberArray addObject:obj1];
}
if ([obj2 isKindOfClass:[NSNumber class]]) {
    [numberArray addObject:obj2];
} else {
    NSLog(@"Object is not of expected type");
}

多线程访问问题

在多线程环境下访问 NSMutableArray 时,如果不进行适当的同步处理,可能会导致数据竞争和不一致的问题。例如,一个线程正在向数组中添加元素,而另一个线程同时尝试删除数组中的元素,可能会导致数组状态混乱。

解决方法是使用锁机制,如 NSLock@synchronized 块来保护对数组的访问。例如,使用 @synchronized 块:

NSMutableArray *sharedArray = [NSMutableArray array];

// 线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(sharedArray) {
        [sharedArray addObject:@1];
    }
});

// 线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(sharedArray) {
        [sharedArray removeObjectAtIndex:0];
    }
});

在上述代码中,@synchronized(sharedArray) 块确保了在同一时间只有一个线程可以访问 sharedArray,从而避免了多线程访问导致的问题。

通过深入了解 NSMutableArray 的特性、操作方法、性能优化、与其他数据结构的交互以及常见问题的解决方法,开发者可以在Objective - C项目中更加高效、稳定地使用可变数组,为应用程序的开发提供有力的数据结构支持。无论是简单的iOS应用开发还是复杂的游戏开发,NSMutableArray 都能在数据管理方面发挥重要作用。在实际编程中,根据具体的需求和场景,合理地运用 NSMutableArray 的各种功能,能够提升程序的性能和稳定性,为用户带来更好的体验。同时,要注意在使用过程中遵循内存管理规则,避免内存泄漏和悬空指针等问题。随着Objective - C语言的不断发展和应用场景的拓展,对 NSMutableArray 等基础数据结构的深入理解和熟练运用将成为开发者必备的技能之一。在处理大量数据或复杂业务逻辑时,合理的数组操作和优化策略可以显著提高程序的运行效率,减少资源消耗。此外,随着多线程编程在移动应用开发中的日益普及,掌握如何在多线程环境下安全地访问 NSMutableArray 至关重要,这不仅涉及到数据的一致性和完整性,还关系到应用程序的稳定性和可靠性。在不同的应用场景中,如数据缓存、列表展示和游戏开发等,要根据实际需求灵活地运用 NSMutableArray 的各种特性,结合其他数据结构和编程技巧,打造出高效、健壮的应用程序。同时,要不断关注Objective - C语言的最新发展和优化方向,以便在开发中更好地利用 NSMutableArray 等数据结构的新特性和改进,提升开发效率和应用质量。例如,随着新的内存管理机制和性能优化技术的出现,开发者可以进一步优化 NSMutableArray 的使用,使其在不同的设备和运行环境下都能发挥最佳性能。在处理复杂数据关系和动态数据变化时,NSMutableArray 的灵活性和可扩展性能够为开发者提供更多的解决方案。通过深入研究和实践,开发者可以更好地掌握 NSMutableArray 在Objective - C编程中的应用,为自己的项目开发带来更多的便利和优势。无论是初学者还是有经验的开发者,不断加深对 NSMutableArray 的理解和运用能力,都有助于提升自己在Objective - C领域的编程水平,开发出更加优秀的应用程序。在实际项目中,遇到与 NSMutableArray 相关的问题时,要善于运用已掌握的知识和方法进行分析和解决,同时也可以参考相关的技术文档、开源项目和社区讨论,获取更多的思路和解决方案。总之,NSMutableArray 作为Objective - C中重要的数据结构,值得开发者深入研究和广泛应用。