3

NSDictionary カテゴリに次のメソッドがあり、ディープ コピーを実行すると正常に動作します。

Xcode 4.1 から 4.2 にアップグレードしたところ、次のように Analyze 関数がこのコードに対して 2 つのアナライザー警告を表示します。

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

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

Ok。これはただ悪いです。必要な情報が翻訳単位で利用できなかったため、リークが発生しました。リークレポートは次のとおりです。

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 に答える