字典的深层副本在 Xcode 4.2 中出现分析错误

Posted

技术标签:

【中文标题】字典的深层副本在 Xcode 4.2 中出现分析错误【英文标题】:Deep copy of dictionaries gives Analyze error in Xcode 4.2 【发布时间】:2011-12-07 22:10:55 【问题描述】:

我在 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。

【问题讨论】:

【参考方案1】:

大概是因为deepCopy没有开始前缀copy

因此,您可能希望更改为 copyWithDeepCopiedValues(或类似的内容),然后查看分析器是否标记了该内容。

更新

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

到目前为止,我使用的唯一属性是 consume,每次我使用这些属性时都是在静态类型的上下文中(例如 C 函数和 C++ 函数和方法)。

尽可能避免使用属性的原因:

1) 为了程序员的利益,坚持约定。代码更清晰,无需参考文档。

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

以下案例都是在启用 ARC 的情况下构建的:

案例#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

头文件.h

extern id NewObject(void);

标题.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 代码上:使用属性会使手动内存管理变得更加困难,以提高程序员的可读性以及可以帮助您的工具(例如编译器、静态分析)。如果程序相当复杂以至于工具无法检测到此类错误,那么您应该重新考虑您的设计,因为调试这些问题对于您或其他人来说同样复杂。

【讨论】:

啊,是的……这确实是问题所在。我重命名为copyDeep,两个警告都消失了。谢谢! @Justin 哇,我没有意识到使用属性的影响。我今天学到了一些东西。 :)【参考方案2】:

添加到@Justin 的答案,您可以告诉编译器-deepCopy returns a retained object 通过将NS_RETURNS_RETAINED 属性附加到方法的声明,如下所示:

- (id) deepCopy NS_RETURNED_RETAINED;

或者,您可以使用 explicitly control the method's "family" 使用 objc_method_family 属性,如下所示:

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

如果你这样做,编译器会知道这个方法在copy家族中,并返回一个复制的值。

【讨论】:

谢谢你,有用的信息。在这种情况下,重命名方法就足够了,但了解其他选项也很好。 当然是一个有用的补充 (+1),但 IMO 这应该是规则的例外。我更喜欢尽可能使用约定。 IIRC,我的代码库中唯一需要的(到目前为止)是consume 是的,我同意在这种情况下使用命名约定解决方案是最好的解决方案,因此我已将其标记为已接受的答案。但是这个答案也很有用。 @Dejal 这绝对是正确的选择,因为属性很脆弱——我正在做一个(相当广泛的)更新来证明这一点。 NS_RETURNED_RETAINED 应该是 NS_RETURNS_RETAINED。我尝试编辑答案,但不允许,因为编辑至少需要 6 个字符。有时 1 或 2 个字符就完全不同了。

以上是关于字典的深层副本在 Xcode 4.2 中出现分析错误的主要内容,如果未能解决你的问题,请参考以下文章

在 4.2 中提交 App Store:XCode 提交验证中出现奇怪的“空白”错误

从 CMSampleBuffer 中提取数据以创建深层副本

如何在 Ruby 中创建对象的深层副本?

如何制作由映射插件创建的剔除对象的深层副本

QVector::replace() 是不是创建深层副本?

如何在 Java 中制作 ArrayList<Integer> 的深层副本? [复制]