在非ARC项目中释放一个在ARC库中声明的对象

6

我正在处理一个非ARC项目,但是使用Philipp Kyeck的socketio库,该库是使用ARC编写的。我正在使用this教程中解释的方法合并非ARC项目和ARC库。

在我的ViewController文件中,我正在使用以下代码初始化socket:

 SockIO *chatSockIO = [[SocketIO alloc] initWithDelegate:self];

当我需要断开连接时,我会打电话。

[chatSockIO disconnect];

这将导致socketIODidDisconnect代理方法被触发。
- (void) socketIODidDisconnect:(SocketIO *)socket{
   [chatSockIO release]; ==> is this call needed?
}

现在我的问题是关于这行代码[chatSockIO release]。在非ARC项目中,我们需要释放一个在ARC模式下定义的对象吗?

当我尝试释放时,会出现异常。

-[SocketIO retain]: message sent to deallocated instance 0x6fec370

但是当我注释掉那一行时,出现内存泄漏,并且我的库对象中的dealloc根本没有被调用。

赏金时间!

忘记我提到的库,我的代码崩溃和泄漏了...在非ARC项目中使用ARC方法定义的对象时,通常的做法是仅分配它,还是在使用后分配并释放它?

编辑:更多信息。

我在崩溃时运行了僵尸检测工具,它显示了对分配和释放函数的调用。

#   Address     Category    Event Type  RefCt   Timestamp       Size    Responsible Library     Responsible Caller
0   0x72d5da0   SocketIO    Malloc      1       00:09.700.274   64      MyProject           -[MyViewController sendRequestForSocketIOPush]
1   0x72d5da0   SocketIO    Retain      2       00:09.700.317   0       MyProject           -[SocketIO initWithDelegate:]
2   0x72d5da0   SocketIO    Release     1       00:09.700.320   0       MyProject           -[SocketIO initWithDelegate:]
3   0x72d5da0   SocketIO    Retain      2       00:09.700.440   0       Foundation          -[NSURLConnectionInternal initWithInfo:]
4   0x72d5da0   SocketIO    Retain      3       00:10.413.717   0       Foundation          -[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:]
5   0x72d5da0   SocketIO    Release     2       00:10.413.761   0       Foundation          -[NSURLConnectionInternalConnection invokeForDelegate:]
6   0x72d5da0   SocketIO    Retain      3       00:10.413.797   0       Foundation          -[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:]
7   0x72d5da0   SocketIO    Release     2       00:10.413.811   0       Foundation          -[NSURLConnectionInternalConnection invokeForDelegate:]
8   0x72d5da0   SocketIO    Retain      3       00:10.413.816   0       Foundation          -[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:]
9   0x72d5da0   SocketIO    Release     2       00:10.415.087   0       Foundation          -[NSURLConnectionInternalConnection invokeForDelegate:]
10  0x72d5da0   SocketIO    Retain      3       00:10.415.214   0       Foundation          -[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:]
11  0x72d5da0   SocketIO    Release     2       00:10.415.216   0       Foundation          -[NSURLConnectionInternalConnection invokeForDelegate:]
12  0x72d5da0   SocketIO    Release     1       00:10.415.275   0       Foundation          -[NSURLConnectionInternalConnection invokeForDelegate:]
13  0x72d5da0   SocketIO    Retain      2       00:10.969.432   0       GraphicsServices    GSEventRunModal
14  0x72d5da0   SocketIO    Release     1       00:10.969.433   0       GraphicsServices    GSEventRunModal
15  0x72d5da0   SocketIO    Retain      2       00:10.969.434   0       GraphicsServices    GSEventRunModal
16  0x72d5da0   SocketIO    Release     1       00:10.969.456   0       GraphicsServices    GSEventRunModal
17  0x72d5da0   SocketIO    Retain      2       00:10.969.459   0       GraphicsServices    GSEventRunModal
18  0x72d5da0   SocketIO    Retain      3       00:10.969.488   0       Foundation          -[NSCFTimer initWithFireDate:interval:target:selector:userInfo:repeats:]
19  0x72d5da0   SocketIO    Release     2       00:10.976.115   0       MyProject           -[SocketIO setTimeout]
20  0x72d5da0   SocketIO    Retain      3       00:10.976.125   0       Foundation          -[NSCFTimer initWithFireDate:interval:target:selector:userInfo:repeats:]
21  0x72d5da0   SocketIO    Release     2       00:10.976.161   0       GraphicsServices    GSEventRunModal
22  0x72d5da0   SocketIO    Retain      3       00:13.935.328   0       GraphicsServices    GSEventRunModal
23  0x72d5da0   SocketIO    Release     2       00:13.935.373   0       MyProject           -[SocketIO setTimeout]
24  0x72d5da0   SocketIO    Retain      3       00:13.935.399   0       Foundation          -[NSCFTimer initWithFireDate:interval:target:selector:userInfo:repeats:]
25  0x72d5da0   SocketIO    Release     2       00:13.935.685   0       MyProject           -[SocketIO onDisconnect]
26  0x72d5da0   SocketIO    Release     1       00:13.935.705   0       MyProject           -[MyViewController socketIODidDisconnect:]
27  0x72d5da0   SocketIO    Release     0       00:13.935.716   0       GraphicsServices    GSEventRunModal
28  0x72d5da0   SocketIO    Zombie      -1      00:13.936.298   0       GraphicsServices    GSEventRunModal

不需要更改 ARC 类文件,只需在类文件的编译器标志中添加“-fobjc-arc”。 - Sumanth
@Sumanth 很不幸,我有一个使用ARC编写的库,而且该库使用了3或4个其他遵循ARC的库。在这种情况下,我想静态库方法更好。即使我使用编译器标志对所有ARC类文件进行分类,但我在项目文件中分配和使用sockIO对象,该对象是以非ARC模式编写的。如果我在非ARC文件中进行alloc / retain操作,则必须释放。 - Krishnabhadra
2
这是一篇关于将你的代码迁移到Objective-C ARC的非常好的教程!另外,你是否知道NS_RETURNS_NON_RETAINEDNS_RETURNS_RETAINED - Oleg Trakhman
不,这不是从我的代码中调用的。 - Krishnabhadra
根据惯例,您不应该释放传递给函数的对象...所以也许库中存在泄漏?看看是否有其他以同样速率泄漏的对象可能会有帮助。(例如运行操作11次,并查找任何泄漏11次的对象--这可能是一个线索)您可能在其他地方泄漏了另一个对象,导致SocketIO堆积。 - nielsbot
显示剩余2条评论
6个回答

3

回答你的问题:

如果你在非ARC代码中使用受ARC管理的对象,你可以像使用非ARC对象一样使用它:如果你创建或保留了它,你必须释放或自动释放它。

关于你的问题:

在你的评论中提到,你尝试通过以下方式初始化来解决问题:

self.chatSockIO = [[[SocketIO alloc] initWithDelegate:self] autorelease];

socketIODidDisconnect中:
self.chatSockIO = nil;

只要chatSockIO属性具有“保留”语义,并且一次仅使用一个SocketIO对象,那么它应该可以正常工作。

Zombie输出提示出了问题的原因:

  • 在倒数第二行,您释放了对象,保留计数减少为0,并且对象被释放。这是您期望的结果。
  • 然而,在最后一行中,试图从运行循环中保留该对象。由于没有NSZombie,您知道这是一次保留。
  • 这意味着尽管您已经完成该对象,但它仍然会从某个地方接收到调用。这是不可预期的。

可能是您代码中的某些内容或者您正在使用的库中存在错误。只是一个直觉:在SocketIO.m中,将这些行替换为-onDisconnect中的行。

if (_webSocket != nil) {
    [_webSocket close];
}

使用

if (_webSocket != nil) {
    [_webSocket close];
    _webSocket.delegate = nil;
}

1

这可能不能解决你的问题,但无论如何,在委托调用中释放对象通常是一个非常糟糕的想法 - 它可能在仍在工作时被dealloc'd,特别是如果对象处于ARC下,则可能没有dealloc方法。

因此,请将您的关闭委托调用转换为以下内容:

- (void) socketIODidDisconnect:(SocketIO *)socket
{
   chatSockIO.delegate = nil; // don't want any more messages
   dispatch_async(dispatch_get_main_gueue(), ^{ self.chatSockIO = nil; }); // typed in text editor
   // your question - is the release needed? Well, under virtually every scenario yes, but I don't know this framework
}

无论这是否解决了您的问题,您都应该按照这种方式在主线程上进行释放 - 在委托返回后。如果您查看您的代码,您应该像处理普通类一样处理此ARC类。互操作性非常好,我在我的ARC应用程序中使用了几个非ARC项目,其他人也成功地做到了相反。

0

根据我的经验,我不会在 ARC 和 non-ARC 之间混合使用,除非只涉及很小的代码块。我曾浪费了太多时间去查找泄漏和崩溃错误。

如果无法将程序转换为 ARC,请考虑使用 sockIO 的 non-ARC 版本:https://github.com/pkyeck/socket.IO-objc/tree/non-arc

这可能会为你节省很多麻烦。


我最初使用的是非 ARC 版本,但它仍然使用 Cocoa-WebSocket,而 ARC 版本升级为使用 SocketRocket。这就是为什么我不得不使用 ARC 版本的原因。 - Krishnabhadra
此外,使用静态库方法在非 ARC 项目中使用 ARC 类是有效的。我可以忽略我提到的泄漏,因为该代码部分仅在我的应用程序中执行一次,并且泄漏的幅度非常小。我只需将释放行注释掉就可以继续进行。但我想知道在使用静态库方法合并 ARC 和非 ARC 文件时,正确的流程是什么。 - Krishnabhadra
我认为您实际上需要释放它。但是也许您在这里的问题是,在运行其余代码时您没有持有对SockIO对象的引用?尝试将sockIO对象添加到一个强属性中并再次添加释放操作。 - Tobias Hieta

0
我曾经遇到过类似的问题。我通过在类中设置@property(nonatomic, retain)来解决了这个问题。在你的情况下,应该是这样的:

在.h文件中

  @property(nonatomic, retain) SockIO *socketIO;

in .m

   @synthesize socketIO;
   chatSockIO = [[SocketIO alloc] initWithDelegate:self];

而且你不会再让你的应用崩溃了!


好的,我按照你的建议做了,我将chatSockIO变成了一个属性并保留它。当我分配内存时,我将代码更改为self.chatSockIO = [[[SocketIO alloc] initWithDelegate:self]autoRelease];。在socketIODidDisconnect委托方法中,我用self.chatSockIO = nil替换了[chatSockIO release]。但是我仍然遇到了崩溃问题。 - Krishnabhadra
不要使用 self.chatSockIO,否则它会被多次保留。示例代码的结构使 ivar 使用保留值设置。 - David H
@DavidH 没有,我正在使用autorelease和self.chatSockIO。你注意到了吗? - Krishnabhadra

0

ARC和非ARC对象可以混合使用。使用ARC实现的类可以安全地从非ARC代码中使用。每当出现所有权错误时,问题可能在您的代码中。

如果没有看到代码,就不可能找到错误,但它可能是一些简单的过度释放。

查看MyProject中仪器跟踪显示Release事件的所有位置,例如-[SocketIO setTimeout]-[SocketIO onDisconnect]-[MyViewController socketIODidDisconnect:]。检查是否存在不当的所有权转移,例如分配由保留属性处理的实例变量或类似情况。


在您提到的3个方法中,只有在-[MyViewController socketIODidDisconnect:]中我才释放socket io对象。它只会被调用一次(这是我首先想到的,认为某种情况下委托会被调用两次以进行一次断开连接,并释放对象两次)。关于完整代码,我没有留下什么东西...对象socketIO的唯一其他用途是在接收或发送数据时。我在那里没有进行任何保留、释放或自动释放。此外,函数setTimeoutonDisconnect是库内部的函数,而不是由我直接调用的。 - Krishnabhadra
这可能是库中的一个错误,它在某个地方释放了过多的东西。虽然我对此并不确定,所以我将其重新表述为一个更通用的问题。 - Krishnabhadra
看起来,GraphicsServices的“GSEventRunModal”函数实际上执行了过度释放。您应该查看这些调用的保留历史中的各个堆栈,以了解为什么执行它们。这可能是您正在使用的库的问题。 - Nikolai Ruhe
当他释放socket对象时,他并未取消代理设置,因此可能会收到两条消息并进行双重释放。我建议他在适当的位置将代理设置为nil(同时在主线程上延迟释放)。 - David H

0

我目前正在处理一个类似的项目,解决方法是将非ARC项目转换为ARC。Xcode会自动转移所有内容,这样就可以解决所有内存泄漏和其他问题。

要转换为ARC,您需要:

1. 点击编辑->重构->转换为Objective-C ARC

2. 点击您的项目目标,在下拉菜单中选择要转换为ARC的文件

3. 选择正确的文件后,点击“检查”

4. 点击下一步,等待转换完成。

5. 单击“更新”按钮以更新您的代码。

6. 检查您的代码

希望这有所帮助。它对我很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接