使用快速枚举在NSMutableArray的副本上删除对象的详细信息

4

我曾经吃过亏,当你遍历一个NSMutableArray对象时,你不能从其中移除对象。

通过循环[< array_object > copy]而不是< array_object >可以解决这个问题。

然而,我有一些未解答的问题,希望Objective-C专家能提供帮助。

  1. In this first for loop, I expected each of the nextObjects to point to different memory (i.e I thought the msgDetail array will have a list of pointers, each pointing to the address of the NSDictionary that a particular array index contains). But all of the %p nextObject prints are giving the same value. Why is that?

    for(NSDictionary *nextObject in msgDetailArray)
    {
    
        NSLog(@"Address = %p, value = %@",&nextObject,nextObject) ;
        //Doing [msgDetailArray removeObject:nextObject] here based on some condition fails
    }
    
  2. In the second for loop, the address of the nextObject NSDictionarys are different from that printed in the first for loop.

    for(id nextObject in [msgDetailArray copy])
    {
    
        NSLog(@"In copy Address = %p, value = %@",&nextObject,nextObject) ;
    
        //Doing [msgDetailArray removeObject:nextObject] here based on some condition succeeds and it also removes it from the original array even though I am looping through a copy
    }
    
  3. However, when I loop through [msgDetailArray copy], and then do a removeObject:, it removes it from the original msgDetailArray. How does removeObject: do this? Does it actually use the contents of the dictionary and remove an object that matches the content? I thought all it does is to check if there is an object that is in the same memory location and remove it (Based on 2, I assume the memory address containing the dictionaries in [msgDetailArray copy] are not the same as the addresses in the original msgDetailArray). If it is actually using contents, I will have to be very careful in case there are duplicate entries.

    for(id nextObject in msgDetailArray)
    {
            NSLog(@"Address = %p, value = %@",&nextObject,nextObject) ;
            //test what is left in msgDetailArray.  I see that doing removeObject on [msgDetailArray copy] does remove it from the original too.  How is removeObject working (is it actually contents of dictionary)
    }
    
3个回答

2
但所有的%p打印都给出了相同的值,为什么会这样呢?
因为&nextObject是nextObject变量的地址,它是您代码示例中使用的循环变量的指针类型。nextObject本身也是一个指针,所以如果您想打印对象的地址,只需要将其转换为void*即可:
NSLog(@"Address = %p, value = %@", (void*)nextObject, nextObject);

在第二个for循环中,nextObject NSDictionaries的地址与第一个for循环中打印的地址不同,因为这是一个不同变量的地址。
因为这是一个不同变量的地址。
当我循环遍历[msgDetailArray copy]并执行removeObject时,它会从原始的msgDetailArray中将其删除。
你循环遍历的副本存储在一个不可见的临时对象中。它是由原始数组支持的不可变副本。
调用[msgDetailArray removeObject:nextObject]作用于原始数组。如果你不希望有此效果,请将复制品存储在一个变量中,并从中删除项目。
NSMutableArray *mutableCopy = [msgDetailArray mutableCopy];
...
[mutableCopy removeObject:nextObject];

这不会触及原始数组。但是,您不能在同时从中删除项目时迭代 mutableCopy


很好的解释。只是确认一下,当我调用[<原始数组> removeObject:<从“浅拷贝”中获取的字典对象>]时,它知道要删除哪个对象,因为它正在使用NSMutableDictionary的内容。是这样吗?如果是的话,我假设这仅适用于某些对象,如NSDictionary,而不适用于所有对象(具有自定义isEqual的对象)。是否有一些术语(例如符合NSCopying协议)可以用来区分我正在处理哪种类型的对象? - Smart Home
所有的解释都很好,我不知道该接受哪一个!我会在明天接受得票最高的那个! - Smart Home
@SmartHome removeObject知道要删除哪个对象,因为它是一个浅拷贝,这意味着原始容器和副本中的字典实际上是内存中相同的对象。它们具有相同的地址、相同的内容等等。这就是为什么删除操作没有问题定位它们的原因。 - Sergey Kalinichenko
谢谢。我做了一个实验,创建了一个NSMutableDictionary,其中两个元素是相同的。调用removeObject:会删除这两个条目。因此,看起来对于NSMutableDictionary,isEqual使用字典元素的内容,并且不仅依赖于对象匹配的内存。 - Smart Home

2
复制数组时会进行“浅复制”。它创建一个新的数组对象,然后存储指向第一个数组中对象的指针。它不会创建新的对象。
把数组想象成一个地址簿。它列出了你朋友家的地址。如果我复制你的地址簿,那么我就有了一份地址列表的副本。地址簿里并没有房子。
如果我从我的地址簿副本中删除一个条目,它不会从你的地址簿中删除同样的条目。它也不会摧毁任何房屋。
在你的循环2代码中,你正在遍历副本,然后告诉你的原始数组删除某些对象。它们从原始数组中被删除,但从副本中并没有删除。(这很好,因为正如你所说,当你迭代数组时改变它会导致崩溃。)
请注意,数组可以包含指向同一对象的多个指针,就像地址可以包含相同的地址一样。(如果一个朋友结婚后改了名字,你可能会在你的地址簿中用她的新名字写入她的地址,并保留她的旧名字的条目。两个地址都指向同一个房子。)
请注意,你正在使用的removeObject方法会删除所有该对象的条目。这就像是在说“删除我的地址簿中123 Elm街的每个条目”。如果你只想删除重复集合中的一个条目,那么你需要使用removeObjectAtIndex,并且你需要改变你的代码使其工作。

很好的解释。只是想确认一下,当我调用 [<原始数组> removeObject:<从“浅拷贝”中获取的字典对象>]时,它知道要删除哪个对象,因为它正在使用NSMutableDictionary的内容。对吗?如果是的话,我假设这仅适用于某些对象,如NSDictionary,而不是所有对象。是否有一些术语(如符合NSCopying协议)可以用来区分我正在处理哪种对象? - Smart Home
所有的解释都很好,我不知道该接受哪一个!我会在明天接受得票最高的那个! - Smart Home
removeObject:方法遍历数组中的所有对象,并使用isEqual:方法将每个对象与传入的对象进行比较。对于自定义对象,您需要实现isEqual:方法。 - Duncan C
谢谢。我做了一个实验,创建了一个NSMutableDictionary,其中两个元素是相同的。调用removeObject:会删除这两个条目。因此,看起来对于NSMutableDictionary,isEqual使用字典元素的内容,而不仅仅依赖于对象匹配的内存。 - Smart Home
不要在多个地方发布相同的评论。 - Duncan C

2
  1. 复制操作复制的是容器对象,而不是容器内容。你会得到一个指向同一对象的新指针列表。

  2. &nextObject 是变量的地址,而不是对象的地址。对象的地址是变量的值:NSLog(@"%p", nextObject);

  3. removeObject: 使用 isEqual: 方法将其参数与集合中的内容进行比较,通常不是通过地址比较,而是根据类定义的某种语义评估来进行比较。

即使忽略在快速枚举循环内修改对象的禁令,你也不能从复制的对象中删除对象,因为 copy 创建的是一个 不可变 的副本。你需要使用 mutableCopy 来获得一个能够被修改的新实例。


很好的解释。只是确认一下,当我调用[<原始数组> removeObject:<从“浅拷贝”中获取的字典对象>]时,它知道要删除哪个对象,因为它正在使用NSMutableDictionary的内容。是这样吗?如果是的话,我假设这仅适用于某些对象,如NSDictionary,而不适用于所有对象(具有自定义isEqual的对象)。是否有一些术语(例如符合NSCopying协议)可以用来区分我正在处理哪种类型的对象? - Smart Home
所有的解释都很好,我不知道该接受哪一个!我会在明天接受得票最高的那个! - Smart Home
1
它知道要删除哪个对象,因为它正在使用NSMutableDictionary的内容。是这样吗?是的,但在这种情况下,它也是完全相同的字典实例,因为我说过内容没有被复制。我可能在我的答案中没有表述清楚。 “我假设这仅适用于某些对象,例如NSDictionary,而不适用于所有对象” 我不确定你在这里的意思。许多类都会重写isEqual:,以便有更有用的比较方式,而不仅仅是“是否是相同的内存块”。 - jscs
谢谢。我做了一个实验,创建了一个NSMutableDictionary,其中两个元素是相同的。调用removeObject:会删除这两个条目。因此,看起来对于NSMutableDictionary,isEqual使用字典元素的内容,而不仅仅依赖于对象匹配的内存。 - Smart Home

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接