2

私はまだ、ARC、ブリッジング、および特定の非通行料金のブリッジ CF オブジェクトについて少し混乱しています。私の現在の混乱は、CFSocket に関するものです。私は正しくクリーンアップしていると確信していますが、分析ツールはそうではないことを教えてくれます。たぶん、私の不協和音のためにリークが見えないか、ツールが間違っているのでしょう。私はツールを非難する準備ができていないので、問題を指摘する他の目を探しています. たとえば、__bridge所有権を自分に譲渡するためのフォームがありませんか?

私のプロジェクトでは、ARC を使用して、TCP ベースのサーバーを使用しています。このクラスを「MyServer」と呼びましょう。MyServer には、次のように定義された内部プロパティ ソケットがあります。

@property (assign) CFSocketRef socket;

このプロパティは、サーバーの実行中にソケット参照を保持します。サーバーを停止すると参照が解放され、サーバー オブジェクトが削除されます。また、サーバーの起動プロセス中に作成される潜在的なリークをクリーンアップしようとしています. 私が静的解析で問題を抱えているのはこの領域です。

サーバーは次の方法で起動されます。

- (BOOL)startServer
{
    BOOL started = NO;

    NSLog(@"[%@ %@] starting server on port %u", NSStringFromClass([self class]), NSStringFromSelector(_cmd),self.port);

    self.lastError = nil;

    if ([self createSocket]) {
        started = YES;
        _state = SERVER_STATE_STARTING;
    };

    return started;
}

このcreateSocketメソッドは、次のようにソケットを作成します (当然)。

-(BOOL)createSocket
{
    BOOL result = YES;

    self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,
                                 IPPROTO_TCP, 0, NULL, NULL);
    if (self.socket != NULL) {
        int reuse = true;
        int fileDescriptor = CFSocketGetNative(self.socket);
        if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR,
                       (void *)&reuse, sizeof(int)) == 0) {

            struct sockaddr_in address;
            memset(&address, 0, sizeof(address));
            address.sin_len = sizeof(address);
            address.sin_family = AF_INET;
            address.sin_addr.s_addr = htonl(INADDR_ANY);
            address.sin_port = htons(self.port);


            CFDataRef addressData = CFDataCreate(NULL,
                                                 (const UInt8 *)&address,
                                                 sizeof(address));

            if (addressData && CFSocketSetAddress(self.socket, addressData) == kCFSocketSuccess) {
                self.listenHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor
                                                                  closeOnDealloc:YES];

                [[NSNotificationCenter defaultCenter] addObserver:self
                                                         selector:@selector(receiveIncomingConnectionNotification:)
                                                             name:NSFileHandleConnectionAcceptedNotification
                                                           object:nil];
                [self.listenHandle acceptConnectionInBackgroundAndNotify];

                _state = SERVER_STATE_RUNNING;
            } else {
                result = NO;
                [self errorWithName:@"Unable to bind socket to address."];
            }
            CFRelease(addressData);
        } else {
            [self errorWithName:@"Unable to set socket options."];
            CFRelease(self.socket);
            CFSocketInvalidate(self.socket);
            CFRelease(self.socket);
            self.socket = nil;
            result = NO;
        }
    } else {
        [self errorWithName:@"Unable to create socket."];
        // CFRelease(self.socket); //NO - CFRelease(NULL) is a runtime error!
        result = NO;
    }

    return result;
}

このコードで静的解析を実行すると、Xcode は self.socket に関する潜在的なリークを多数報告します。createSocket上記の方法からの一例を次に示します。

(静的解析エラー)

このパスでオブジェクトをもう参照していないことは確かです。おそらく、私がオブジェクトを所有したいことをシステムに伝える何らかの方法があり、システムが不平を言う理由は、私が所有していることをシステムが認識できないためです。__bridgeその情報を伝えるためにキャストの1つを使用することになっていますか? プロパティを保持または強力にしようとしましたが、オブジェクトではないため構築されません。他の考えはありますか?

4

3 に答える 3

3

いくつかの考え:

  1. Jesse が指摘するように、問題は、アクセサー メソッドを使用していてsocket、メソッドに渡されたオブジェクトがリークしていると考えて、アナライザーが少し混乱setSocketし、インスタンス変数に保存していることに気付かないことです。 . これらの出現箇所を に置き換えるself.socket_socket、関連する警告self.socketが消えます。

  2. addressDataあなたのコードは、実行パスが でaddressDataある可能性があるがNULL、まだ を呼び出しているという事実に関連することに関する2 番目の警告を生成しますCFReleaseNULLしようとする前に、コードで not を明示的にチェックする必要がありますCFRelease

  3. ソケットを無効にする前に 1 回、無効にした後に 2 回、ソケットを解放しています。明らかに、それを 2 回リリースしたくないでしょう。また、ソケットをNULLではなくに設定することをお勧めしますが、それほどnil重要ではありません。

  4. 部分的には、ポイント 2 に関連する私の変更 (そうでなければ、 if wasの場合は別のelse句を追加する必要がある) が原因ですが、ロジックの一般的なシフトとしても、失敗に関連付けられた多くの実行パスがありますが、関連付けられているのは 1 つだけであるためです。成功した場合は、その単一の成功した実行パスでデフォルトにして に設定することをお勧めします。これにより、作成は成功したがリッスンが成功しなかったすべての異なるパスで、ソケットを無効にして解放することが保証されます。以前は、適切にカバーされていなかった実行パスがいくつかあったと思います。addressDataNULLresultNOYES

したがって、私はこの表現で終わりますcreateSocket

-(BOOL)createSocket
{
    BOOL result = NO;

    _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,
                                 IPPROTO_TCP, 0, NULL, NULL);

    if (_socket != NULL) {
        int reuse = true;
        int fileDescriptor = CFSocketGetNative(self.socket);
        if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR,
                       (void *)&reuse, sizeof(int)) == 0) {

            struct sockaddr_in address;
            memset(&address, 0, sizeof(address));
            address.sin_len = sizeof(address);
            address.sin_family = AF_INET;
            address.sin_addr.s_addr = htonl(INADDR_ANY);
            address.sin_port = htons(self.port);


            CFDataRef addressData = CFDataCreate(NULL,
                                                 (const UInt8 *)&address,
                                                 sizeof(address));

            if (addressData) {
                if (CFSocketSetAddress(_socket, addressData) == kCFSocketSuccess) {
                    self.listenHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor
                                                                      closeOnDealloc:YES];

                    [[NSNotificationCenter defaultCenter] addObserver:self
                                                             selector:@selector(receiveIncomingConnectionNotification:)
                                                                 name:NSFileHandleConnectionAcceptedNotification
                                                               object:nil];
                    [self.listenHandle acceptConnectionInBackgroundAndNotify];

                    result = YES;
                    _state = SERVER_STATE_RUNNING;
                } else {
                    [self errorWithName:@"Unable to bind socket to address."];
                }
                CFRelease(addressData);
            }
        }

        if (result != YES) {
            [self errorWithName:@"Unable to set socket options."];
            CFSocketInvalidate(_socket);
            CFRelease(_socket);
            _socket = NULL;
        }
    } else {
        [self errorWithName:@"Unable to create socket."];
    }

    return result;
}

私の最初の回答は、Core Foundation メモリ管理の基本に焦点を当てることでしたが、重要ではありますが、目前の問題にすぐには関係ありません。

元の回答:

はい、Core Foundation 関数呼び出しの名前にCreateorが含まれている場合は、オブジェクトを所有しています。Copyそのため、次のいずれかを行う必要があります。

明らかに、ここでは前者が適用されますが、一般的にはどちらのアプローチも機能します。

于 2013-01-13T18:31:38.547 に答える
2

ここでの問題は、コンパイラを混乱させるプロパティを使用していることです。( callself.socket = ...が実際に[self setSocket:...]既に保持されているアイテムを期待して格納していることを理解できません。

インスタンス変数を直接使用すると、その CFSocketRef への参照を独自に保持していることを認識できるため、警告が表示されなくなります。(この場合、プロパティはプライベートで割り当てられているため、インスタンス変数を使用することもできます。)

また、後で CFRelease を呼び出した後にソケットを使用しています (CFRelease の後に CFSocketInvalidate を呼び出しています)。これは悪い考えです。

于 2013-01-13T19:45:05.510 に答える
1

潜在的なリークであるため、潜在的なリークとして報告されています。メソッドが 2 回呼び出されると、createSocketソケットを解放しないため、リークが発生します。

追加してみてください:

CFRelease(self.socket);

への呼び出しの直前CFSocketCreateCFReleaseメソッドのソケットで呼び出していることを確認してくださいdealloc

于 2013-01-13T17:52:06.360 に答える