字典的深层副本给Xcode 4.2分析错误

我有一个NSDictionary类别中的以下方法,做一个深层复制,这工作正常。

我刚从Xcode 4.1升级到4.2,Analyze函数给出了这个代码的两个分析器警告,如下所示:

- (id)deepCopy; { id dict = [[NSMutableDictionary alloc] init]; id copy; for (id key in self) { id object = [self objectForKey:key]; if ([object respondsToSelector:@selector(deepCopy)]) copy = [object deepCopy]; else copy = [object copy]; [dict setObject:copy forKey:key]; // Both -deepCopy and -copy retain the object, and so does -setObject:forKey:, so need to -release: [copy release]; // Xcode 4.2's Analyze says this is an incorrect decrement of the reference count?! } return dict; // Xcode 4.2's Analyze says this is a potential leak } 

Xcode的分析器中是否存在这些错误,或者我可以做些什么改变来避免这些警告?

我还没有使用ARC,但是如果需要为此方法支持ARC,还需要进行其他更改。

据推测,这是因为deepCopy不以前缀copy 开始

所以你可能想要改变像copyWithDeepCopiedValues (或类似的东西),然后看看分析器标记。

更新

正如Alexsander指出的那样,您可以使用属性来表示引用计数意图。 这应该(IMO)是规则的例外,并且很less使用。 就个人而言,我不会使用objc方法的属性,因为它是脆弱的。

我迄今为止唯一使用的属性已被consume ,并且每次使用这些属性都是静态types的上下文(例如C函数和C ++函数和方法)。

如果可能,应该避免属性的原因:

1)为了程序员的缘故,坚持约定。 代码更清晰,你不需要参考文档。

2)方法是脆弱的。 您仍然可以引入引用计数不平衡,并且可以使用属性来引入由于属性冲突而引起的构build错误。

以下情况都是在启用ARC的情况下构build的:

情况1

 #import <Foundation/Foundation.h> @interface MONType : NSObject - (NSString *)string __attribute__((objc_method_family(copy))); @end @implementation MONType - (NSString *)string { NSMutableString * ret = [NSMutableString new]; [ret appendString:@"MONType"]; return ret; } @end int main (int argc, const char * argv[]) { @autoreleasepool { id obj = nil; if (random() % 2U) { obj = [[NSAttributedString alloc] initWithString:@"NSAttributedString"]; } else { obj = [MONType new]; } NSLog(@"Result: %@, %@", obj, [obj string]); } /* this tool's name is ARC, dump the leaks: */ system("leaks ARC"); return 0; } 

此程序产生以下错误: error: multiple methods named 'string' found with mismatched result, parameter type or attributes

太棒了,编译器正在尽其所能来防止这些问题。 这意味着属性冲突会导致翻译错误。 这是不好的,因为当非平凡的代码库被组合和属性冲突,你将有错误纠正和程序更新。 这也意味着,只要在翻译单元中包含其他库,就可以在使用属性时破坏现有的程序。

案例#2

Header.h

 extern id NewObject(void); 

Header.m

 #import <Foundation/Foundation.h> #import "Header.h" @interface MONType : NSObject - (NSString *)string __attribute__((objc_method_family(copy))); @end @implementation MONType - (NSString *)string { NSMutableString * ret = [NSMutableString new]; [ret appendString:@"-[MONType string]"]; return ret; } @end id NewObject(void) { id obj = nil; if (random() % 2U) { obj = [[NSAttributedString alloc] initWithString:@"NSAttributedString"]; } else { obj = [MONType new]; } return obj; } 

的main.m

 #import <Foundation/Foundation.h> #import "Header.h" int main (int argc, const char * argv[]) { @autoreleasepool { for (size_t idx = 0; idx < 8; ++idx) { id obj = NewObject(); NSLog(@"Result: %@, %@", obj, [obj string]); } } /* this tool's name is ARC, dump the leaks: */ system("leaks ARC"); return 0; } 

好。 这太糟糕了 。 我们已经引入了泄漏,因为翻译单元中没有必要的信息。 这是泄漏报告:

 leaks Report Version: 2.0 Process 7778: 1230 nodes malloced for 210 KB Process 7778: 4 leaks for 192 total leaked bytes. Leak: 0x1005001f0 size=64 zone: DefaultMallocZone_0x100003000 __NSCFString ObjC CoreFoundation mutable non-inline: "-[MONType string]" Leak: 0x100500320 size=64 zone: DefaultMallocZone_0x100003000 __NSCFString ObjC CoreFoundation mutable non-inline: "-[MONType string]" Leak: 0x100500230 size=32 zone: DefaultMallocZone_0x100003000 has-length-byte: "-[MONType string]" Leak: 0x100500390 size=32 zone: DefaultMallocZone_0x100003000 has-length-byte: "-[MONType string]" 

注意:计数可能不同,因为我们使用random()

这意味着因为MONTypemain()是不可见的,所以编译器将ARC属性绑定到当前TU可见的方法(即来自Foundation的声明中的string ,所有这些都遵循约定)。 因此,编译器弄错了,我们能够将泄漏引入到我们的程序中。

案例3

使用类似的方法,我也能够引入负面引用计数不平衡(过早发布,或僵尸信息)。

注意:代码没有提供,因为情况2已经说明了如何完成引用计数不平衡。

结论

你可以避免所有这些问题,并通过坚持惯例而不是使用属性来提高可读性和可维护性。

使对话返回到非ARC代码:使用属性使得手动内存pipe理对于程序员的可读性以及那些可以帮助你的工具(例如编译器,静态分析)来说更加困难。 如果程序适当复杂,以至于工具无法检测到这些错误,那么您应该重新考虑您的devise,因为对于您或其他人来说,debugging这些问题同样复杂。

添加@贾斯汀的答案,你可以告诉编译器, -deepCopy通过附加NS_RETURNS_RETAINED属性的方法的声明返回一个保留的对象 ,如下所示:

 - (id) deepCopy NS_RETURNED_RETAINED; 

或者,您可以使用objc_method_family属性显式控制方法的“族” , objc_method_family所示:

 - (id) deepCopy __attribute__((objc_method_family(copy))); 

如果你这样做,编译器就会知道这个方法在copy族中并且返回一个拷贝的值。

Interesting Posts