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

Dej*_*jal 3 cocoa deep-copy analyzer clang-static-analyzer xcode4.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
}
Run Code Online (Sandbox Code Playgroud)

这些错误是在Xcode的分析器中,还是我可以做出改变来避免这些警告?

我还没有使用ARC,但我感兴趣的是,如果需要对此方法支持ARC进行其他更改.

jus*_*tin 11

据推测,这是因为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;
}
Run Code Online (Sandbox Code Playgroud)

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

很好,编译器正在尽其所能来防止这些问题.这意味着属性中的冲突可能会导致基于翻译的错误.这很糟糕,因为当组合非重要的代码库并且属性发生冲突时,您将需要更正错误并更新程序.这也意味着在使用属性时,简单地在翻译单元中包含其他库可能会破坏现有程序.

案例#2

Header.h

extern id NewObject(void);
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

的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;
}
Run Code Online (Sandbox Code Playgroud)

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

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]"
Run Code Online (Sandbox Code Playgroud)

注意:计数可能因我们使用而有所不同 random()

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

案例3

使用类似的方法,我还能够引入负参考计数不平衡(过早发布或消息僵尸).

注意:未提供代码,因为案例#2已经说明了如何实现引用计数不平衡.

结论

您可以通过遵循约定而不是使用属性来避免所有这些问题并提高可读性和可维护性.

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


Ale*_*ers 6

添加到@ Justin的答案,您可以通过将属性附加到方法的声明来告诉编译器-deepCopy 返回保留对象,NS_RETURNS_RETAINED如下所示:

- (id) deepCopy NS_RETURNED_RETAINED;
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用如下属性显式控制方法的"族"objc_method_family:

- (id) deepCopy __attribute__((objc_method_family(copy)));
Run Code Online (Sandbox Code Playgroud)

如果这样做,编译器将知道此方法在该copy系列中并返回复制的值.