2

我仍然对 ARC、桥接和某些非免费桥接 CF 对象感到有些困惑。我目前的困惑是围绕 CFSocket。我很确定我清理得正确,但分析工具告诉我不然。也许我的不和谐让我看不到泄漏,或者工具是错误的。我还没准备好责怪这个工具,所以我正在寻找其他人来指出这个问题。例如,我是否缺少__bridge将所有权转让给我的形式?

在我的项目中,使用 ARC,我有一个基于 TCP 的服务器。我们称这个类为“MyServer”。在 MyServer 中,我有一个内部属性 socket,定义如下:

@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方法创建一个套接字(duh),如下所示:

-(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演员来传达该信息吗?我尝试使属性保持或强大,但这并没有建立,因为它不是一个对象。还有其他想法吗?

4

3 回答 3

3

几个想法:

  1. 正如 Jesse 指出的那样,问题在于您正在使用访问器方法,socket而分析器有点困惑,认为传递给该setSocket方法的对象正在泄漏,而没有意识到您将其保存在实例变量中. 如果您将这些出现替换为self.socket_socket您的相关警告self.socket就会消失。

  2. 您的代码会生成第二个警告,该警告addressData与您有一个执行路径addressData可能是相关的事实NULL,但您仍在调用CFRelease. NULL在尝试之前,您应该让代码明确检查 not CFRelease

  3. 你释放了你的套接字两次,一次是在它失效之前,一次是在它失效之后。显然,您不想发布两次。我还建议将套接字设置为NULL, not nil, 不是那么重要。

  4. 部分原因是我与第 2 点相关的更改(否则我需要添加另一个else子句 if addressDatawas NULL),但也作为逻辑上的一般转变,因为您有许多与失败相关的执行路径,但只有一个与成功,我建议默认并将其设置为result单个成功的执行路径。这确保我们在创建成功但侦听未成功的所有不同路径中使套接字无效并释放。我相信以前有一些执行路径没有被正确覆盖。NOYES

因此,我最终得到了以下版本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 函数调用的名称中包含CreateCopy名称中,则您拥有该对象。因此,您必须:

显然,前者在这里适用,但一般来说,任何一种方法都有效。

于 2013-01-13T18:31:38.547 回答
2

这里的问题是您正在使用一个属性,这会使编译器感到困惑。(它无法弄清楚 call self.socket = ...,它实际上[self setSocket:...]需要一个已经保留的项目并存储它。

如果您直接使用实例变量,它应该消除警告,因为它会理解您自己持有对该 CFSocketRef 的引用。(在这种情况下,由于您的属性是私有的且已分配,因此您也可以使用实例变量。)

此外,您在稍后调用 CFRelease 之后使用套接字(您在 CFRelease 之后调用 CFSocketInvalidate),这是一个坏主意。

于 2013-01-13T19:45:05.510 回答
1

它被报告为潜在泄漏,因为它是潜在泄漏。如果该createSocket方法被调用了两次,那么就会发生泄漏,因为您从未释放过套接字。

尝试添加:

CFRelease(self.socket);

就在调用CFSocketCreate. 并确保您CFRelease在方法中调用套接字dealloc

于 2013-01-13T17:52:06.360 回答