如何在不更改原始内容的情况下操作 NSDictionary 内容的副本
Posted
技术标签:
【中文标题】如何在不更改原始内容的情况下操作 NSDictionary 内容的副本【英文标题】:How do I manipulate a copy of NSDictionary's contents without changing the contents of original 【发布时间】:2015-12-07 07:03:40 【问题描述】:编辑
以下“更简单”的代码按预期工作。请参阅下面的代码,其中解释了我在 cmets 中的观察结果。
- (void) changeDictValue
//If the method just uses the line below, then calling changeDictValue has no impact on self.bcastSeqNumList
NSMutableDictionary *seqListToUpdate = [NSMutableDictionary dictionaryWithDictionary:self.bcastSeqNumList] ;
//But if it instead uses the line below, calling changeDictValue does change self.bcastSeqNumList
NSMutableDictionary *seqListToUpdate = self.bcastSeqNumList ;
seqListToUpdate[@(lastContSeqType)]=@(0) ;
seqListToUpdate[@(maxRcvdSeqType)]=@(1) ;
seqListToUpdate[@(missingSeqType)]=nil ;
所以,看起来我下面的代码中有一些错误(或者在调用 dictionaryWithDictionary 期间如何处理 NSSet 的一些特殊 Obj-C 逻辑:)导致与 missingSeqType 键关联的 NSSet 受到影响,即使我正在对从 self.bcastSeqNumList 派生的新字典进行操作。有什么线索吗?
结束编辑
我有一个代表序列号的NSDictionary
。
1.一个键(lastContSeqType)代表最大的序列号X,这样我就收到了所有的序列号
我有一个这样的字典用于“广播消息”,并且有一个这样的字典用于每个带有远端的单播消息。
当我从服务器获取存储的序列号列表时,我使用存储在我端的序列号列表和接收到的序列号列表来确定要检索的序列号。
当我执行以下代码行时,
在 getMissingSequenceNumsFromServer:(NSDictionary *) dict 的第一个实现中,一切正常,bcastSeqNumList
正在更新,lastContSeqType
更新为 4,missingSeqType
为空.
在第二个实现中,我从 self.bcastSeqNumList
的内容创建一个新字典,然后操作这个“复制的”字典,我不希望 self.bcastSeqNumList
受到任何操作的影响方法里面。我看到 lastContSeqType
键不受影响,但 missingSeqType
键受到影响并变成一个空列表(即 NSNumber
对象 @(4) 正在被删除)。为什么会这样?
self.bcastSeqNumList= [@@(lastContSeqType) : @(1), @(maxRcvdSeqType) : @(6), @(missingSeqType) : [NSMutableSet setWithObject:@(4)]
mutableCopy];
NSLog(@"Bcast seq num list = %@",self.bcastSeqNumList) ;
NSMutableDictionary *rcvdDict= [@@"currBroadcastSeqNumStart" : @(5), @"currBroadcastSeqNumEnd" : @(6)
mutableCopy];
[self getMissingSequenceNumsFromServer:rcvdDict] ;
NSLog(@"Bcast seq num list = %@",self.bcastSeqNumList) ;
案例 1 实施
- (void) getMissingSequenceNumsFromServer:(NSDictionary *) dict
NSInteger serverStoreSeqStart=[dict[@"currBroadcastSeqNumStart"] integerValue] ;
NSInteger serverStoreSeqEnd=[dict[@"currBroadcastSeqNumEnd"] integerValue] ;
NSMutableDictionary *seqListToUpdate = self.bcastSeqNumList ;
NSInteger mySeq = [seqListToUpdate[@(lastContSeqType)] integerValue] ;
if(mySeq < serverStoreSeqStart-1)
//Ask for all stored messages in server
//[self getMessagesfromStart:serverStoreSeqStart toEnd:serverStoreSeqEnd] ;
NSLog(@"Getting messages from %ld and to %ld",serverStoreSeqStart,serverStoreSeqEnd) ;
NSLog(@"Never received seq nums %ld to %ld",mySeq+1,serverStoreSeqStart-1) ;
NSInteger nextSeqToRemove ;
for(nextSeqToRemove=mySeq+1;nextSeqToRemove<=serverStoreSeqStart-1;nextSeqToRemove++)
if([seqListToUpdate objectForKey:@(missingSeqType)])
if([seqListToUpdate[@(missingSeqType)] containsObject:[NSNumber numberWithInteger:nextSeqToRemove]])
NSLog(@"SeqNum %ld is in missing seq num list and being removed since we got server seq %ld > than this",(long)nextSeqToRemove,(long)serverStoreSeqStart) ;
//Remove it
[seqListToUpdate[@(missingSeqType)] removeObject:@(nextSeqToRemove)] ;//
//If set is empty
if([seqListToUpdate[@(missingSeqType)] count] ==0)
//Nothing left in missing seq table. set it to nil and return
[seqListToUpdate removeObjectForKey:@(missingSeqType)] ;
seqListToUpdate[@(lastContSeqType)]=seqListToUpdate[@(maxRcvdSeqType)] ;
NSLog(@"Missing seq num list empty. Setting last contig to max = %@",seqListToUpdate[@(lastContSeqType)]) ;
break ;
else
//mising Seq Type has no elements, nothing to remove
break ;
//At the end..if serverSeqToStore >= maxSeq, then lastCOnt=maxSeq and missing seq empty.
//Else...if missing seq Empty, then
seqListToUpdate[@(lastContSeqType)] = [NSNumber numberWithInteger:serverStoreSeqStart-1] ;
if(seqListToUpdate[@(maxRcvdSeqType)] <= seqListToUpdate[@(lastContSeqType)])
seqListToUpdate[@(maxRcvdSeqType)] = seqListToUpdate[@(lastContSeqType)] ;
//Set to misisng seq num list = nil
else
//remove seqnums
else if (mySeq < serverStoreSeqEnd)
//[self getMessagesfromStart:mySeq+1 toEnd:serverStoreSeqEnd] ;
NSLog(@"Getting messages from %ld and to %ld",mySeq+1,serverStoreSeqEnd) ;
else if (mySeq > serverStoreSeqEnd)
NSLog(@"My stored sequence number %ld exceeds the max stored at server %ld",(long)mySeq,(long)serverStoreSeqEnd) ;
案例 2 实施
下面一行
seqListToUpdate = self.bcastSeqNumList ;
替换为下面的行,以便对seqListToUpdate
的任何更改都不会影响self.bcastSeqNumList
的内容
seqListToUpdate = [NSMutableDictionary dictionaryWithDictionary:self.bcastSeqNumList] ;
【问题讨论】:
很难遵循您的代码,但是missingSeqType
键的字典中存储了什么?它是一个数组还是另一个字典?复制字典不会复制其中的对象;字典的副本将包含与原始字典相同的对象引用,所以如果你操作一个数组,你只是在操作两个字典引用的同一个数组
是的,因为新字典包含对同一集合的引用,所以您不是在更改字典,而是在更改集合
谢谢。尝试了代码的简化版本,请参阅编辑。使用此代码,我看到对新字典的更改不会影响创建它的字典。我想我将不得不使用 - initWithDictionary:copyItems:YES,使新字典独立于原始字典,但根据我对简化代码的实验结果,这似乎是不必要的。看起来我们需要一些 Obj-C 大师来解释发生了什么!
谢谢。你能解释一下你的新评论吗?即假设我使用dictionaryWithDictionary 从self.bcastSeqNumList 创建一个新词典。然后我更改了这个新字典中的一个值(恰好是 NSSet / NSArray 等),您是否希望这些更改会影响 self.bcastSeqNumList 中的相同值?
字典包含对对象的引用,因此当您复制字典时,新字典包含相同的对象引用,因此如果我更改引用的对象(在您的情况下为集合),那么所有包含该对象的字典参考将看到变化,因为只有一组。如果我为新字典中的键分配一个新集合,那么现在有两个对象,我可以独立更改它们。
【参考方案1】:
您必须进行深层复制。目前,您只是将引用复制到相同的对象。您还必须复制字典中引用的对象。
easiest way of doing a deep copy is using NSKeyedArchiver
用于存档和取消存档。
NSDictionary* deepCopy = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:originalDict]];
【讨论】:
谢谢。是否有一个语句可以执行此深层复制?还是我必须从现有对象创建一个新对象,然后将其添加到新字典中? 您可以使用我刚刚添加到答案中的NSKeyedArchiver
。您可能必须编写符合NSCoding
的方法。或者您可以编写自己的深拷贝方法。
感谢这项工作。即使我不做深拷贝,看起来如果 key 有一个 NSNumber 作为它的值,它会自动被深拷贝。即,如果我更改与新字典中的键关联的 NSNumber,则原始字典不会更改。这是为什么呢?
这可能是优化的副作用。 NSNumber
的值不存储在对象中,而是使用 tagged pointers 直接存储在指针中。
感谢您的解释。以上是关于如何在不更改原始内容的情况下操作 NSDictionary 内容的副本的主要内容,如果未能解决你的问题,请参考以下文章