我仍然对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
演员表来传达这些信息吗?我试图使属性保持或强大,但这不构建,因为它不是一个对象。还有其他想法吗?
答案 0 :(得分:3)
有几点想法:
正如Jesse所指出的那样,问题在于你正在使用socket
的访问器方法,并且分析器有点困惑,认为对象传递给setSocket
方法正在泄漏,没有意识到你将它保存在实例变量中。如果您将这些self.socket
替换为_socket
,则与self.socket
相关的警告会消失。
您的代码会生成第二条警告addressData
与您有addressData
可能NULL
的执行路径这一事实有关,但您仍在调用CFRelease
1}}。在尝试NULL
之前,您应该明确检查代码CFRelease
。
你将套接字释放两次,一次使其无效,之后再次释放。显然,你不想释放那两次。我还建议将套接字设置为NULL
,而不是nil
,而不是那么重要。
部分原因是我的更改与第2点相关(如果else
为addressData
,我还需要添加另一个NULL
子句)逻辑上的一般转变,因为你有许多与失败相关的执行路径,但只有一个与成功相关联,我建议将result
默认为NO
并将其设置为YES
in单一成功的执行路径。这可以确保我们在创建成功的所有不同路径中使套接字无效并释放,但是侦听没有成功。我相信之前有一些执行路径未被正确覆盖。
因此,我最终得到了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函数调用名称中包含Create
或Copy
,则您拥有该对象。因此,您必须:
通过CFRelease
释放对象。见Core Foundation Memory Management Programming Guide。
通过CFBridgingRelease
或__bridge_transfer
将所有权转移到ARC。请参阅Transitioning to ARC Release Notes。
显然,前者适用于此,但总的来说,这两种方法都适用。
答案 1 :(得分:2)
这里的问题是您正在使用属性,这会使编译器感到困惑。 (它无法确定调用self.socket = ...
,实际上是[self setSocket:...]
期望已保留的项目并将其存储。
如果直接使用实例变量,它应该使警告静音,因为它会理解您自己持有对该CFSocketRef的引用。 (在这种情况下,由于您的属性是私有的并且已分配,因此您最好使用实例变量。)
此外,您稍后在调用CFRelease后使用套接字(您在CFRelease之后调用CFSocketInvalidate),这是一个坏主意。
答案 2 :(得分:1)
它被报告为潜在泄漏,因为它是潜在的泄漏。如果createSocket
方法被调用两次,则会发生泄漏,因为您永远不会释放套接字。
尝试添加:
CFRelease(self.socket);
在致电CFSocketCreate
之前。并确保您使用CFRelease
方法在套接字上调用dealloc
。