3

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

4

2 回答 2

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;
}

该程序产生以下错误: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;
}

主文件

#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()

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

案例3

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

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

结论

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

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

于 2011-10-14T16:52:04.910 回答
6

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

- (id) deepCopy NS_RETURNED_RETAINED;

或者,您可以使用如下属性显式控制方法的“系列” :objc_method_family

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

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

于 2011-10-14T16:57:10.040 回答