为什么这被标记为使用ARC的潜在泄漏?

时间:2013-01-13 17:45:43

标签: ios memory-leaks automatic-ref-counting static-analysis

我仍然对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方法中的一个示例:

(static analysis error)

确实,我不再在此路径中引用该对象。也许有一些方法告诉系统我想要拥有该对象,并且它抱怨的原因是它不能告诉我这样做。我应该使用其中一个__bridge演员表来传达这些信息吗?我试图使属性保持或强大,但这不构建,因为它不是一个对象。还有其他想法吗?

3 个答案:

答案 0 :(得分:3)

有几点想法:

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

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

  3. 你将套接字释放两次,一次使其无效,之后再次释放。显然,你不想释放那两次。我还建议将套接字设置为NULL,而不是nil,而不是那么重要。

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

  5. 因此,我最终得到了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,则您拥有该对象。因此,您必须:

    显然,前者适用于此,但总的来说,这两种方法都适用。

答案 1 :(得分:2)

这里的问题是您正在使用属性,这会使编译器感到困惑。 (它无法确定调用self.socket = ...,实际上是[self setSocket:...]期望已保留的项目并将其存储。

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

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

答案 2 :(得分:1)

它被报告为潜在泄漏,因为它是潜在的泄漏。如果createSocket方法被调用两次,则会发生泄漏,因为您永远不会释放套接字。

尝试添加:

CFRelease(self.socket);

在致电CFSocketCreate之前。并确保您使用CFRelease方法在套接字上调用dealloc