SKPhysicsBody bodyWithPolygonFromPath内存泄漏问题

9

当我使用自定义形状创建Sprite Kit物理体时,出现了奇怪的内存泄漏。这是我的实现方式:

CGFloat offsetX = self.frame.size.width * self.anchorPoint.x;
CGFloat offsetY = self.frame.size.height * self.anchorPoint.y;

CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 4 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 66 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 35 - offsetX, 57 - offsetY);
CGPathCloseSubpath(path);

self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path];

CGPathRelease(path);

所有的操作都在SKSpriteNode方法内执行。创建这些物体之后,Instruments告诉我有几个内存泄漏:

Leaked object: 
  Malloc 32 Bytes
Size:
  32 Bytes
Responsible Library: 
  PhysicsKit
Responsible Frame:
  std::__1::__split_buffer<PKPoint, std::__1::allocator<PKPoint>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<PKPoint>&)

CGPathRelease(path);这一行是必要的 - 如果没有它,我会得到更多有关CGPath的内存泄漏问题,这是可以理解的。当我使用这种实现(用于测试目的)时:

CGFloat radius = MAX(self.frame.size.width, self.frame.size.height) * 0.5f;
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:radius];

一切都运作良好,没有内存泄漏。我想知道这是Sprite Kit的错误还是我做错了什么。


我不知道你能做什么不同的事情... 我认为这显然必须与调用bodyWithPolygonFromPath有关... 也许你可以在苹果开发者论坛上询问? - davecom
调用树,如果有用的话:[SKPhysicsBody bodyWithPolygonFromPath:], [PKPhysicsBody bodyWithPolygonFromPath:], [PKPhysicsBody initWithPolygonFromPath:], [CGPathApply], CG::Path::Sequence::apply, CG::Path::Subpath::apply, CG::Chunk::apply, ::adaptor::callback, PKPathApplyCGPath, std::__1::vector::__push_back_slow_path, std::__1::__split_buffer::__split_buffer. - Joseph Mansfield
我有同样的问题,只不过是在bodyWithEdgeChain上 - 看起来这是Sprite Kit在处理带有SKPhysicsBody和SKShapeNode的CGPath时的一个bug。 - Rahul Iyer
我无法重现这个问题。这个问题在设备和模拟器上都出现了吗?模拟器似乎有一些设备没有的泄漏问题。 - godel9
5个回答

4

这是Sprite Kit中的一个bug。您需要等待修复。


1
这个 bug 是否已经向苹果报告了?我也遇到了同样的问题。 - Kyle Decot
1
我之前曾经报告过这个问题,但随后便停止使用Sprite Kit了,所以我并不知道它是否已在7.1中被修复。 - Rahul Iyer

2

如果你做以下操作,会发生什么变化吗?

CGPathRef pathCopy = CGPathCreateCopy(path);
CGPathRelease(path);
self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathCopy];
CGPathRelease(pathCopy);

2

正如在多个地方评论的那样,这看起来像是SKPhysicsBody实现中的一个bug,至少持续到iOS 7.1。原因是:

SKPhysicsBody拥有一个实例变量'_path',它保存了调用'bodyWithEdgeChainFromPath'或类似构造函数时传递的初始CGPathRef的副本。该实例变量从未被释放,因此所有路径都将留在内存中。

但是,您可以通过以下方法实现解决方法:

(1) 子类化SKShapeNode,该节点应该持有SKPhysicsBody

(2) 创建并分配此节点的SKPhysicsBody后,检索引用SKPhysicsBody的CGPathRef的实例变量

(3) 当形状节点被释放时,检查路径的保留计数。如果它大于0,则释放它,内存泄漏就消失了。

代码开销很小(除了子类化所有使用依赖于CGPath的物理体的形状节点之外)。只需执行以下操作:

向子类添加实例变量:

{
    CGPathRef myPath;
}

在任何子类化的SKShapeNode实现中实现检索此CGPath值的方法。您还可以考虑将其作为SKNode的一般类别添加:

- (CGPathRef) getPhysicsBodyPath
{
    CGPathRef path = nil;
    if ( self.physicsBody ){
        object_getInstanceVariable(self.physicsBody, "_path", (void**) &path);
    }
    return(path);
}

这段代码将返回节点物理体使用的CGPathRef实例。请注意,这必须在将物理体分配给节点后立即完成。在稍后的时间(即在dealloc()中),可能会返回空值。因此,在创建物体之后,请将该值存储在实例变量'myPath'中。为了使这段代码即使在Apple修复错误后仍能正常工作,我们将添加一个额外的retain,以确保在我们的SKNode被释放后仍然可以访问该对象(见下文):

    /*
     *  we need to keep a copy of the final path reference, that has been created for the physics body.
     *  retrieving this value during deallocation won't work any more...
     */
    myPath = CFRetain([self getPhysicsBodyPath]);

最后,覆盖“dealloc”方法并在您的SKNode被释放后释放路径:
- (void) dealloc
{
    self.physicsBody = nil;
    [super dealloc];
    if ( myPath != nil ) {
        /* this will work, because we've retained the path for this instance */
        CFIndex rc = CFGetRetainCount (myPath);
        /* this is our own release ... */
        CGPathRelease(myPath);
        /* in case Apple has fixed the release, this is OK, otherwise we'll need
         * to release the path twice to avoid memory leaks
         */
        if ( rc > 1 ) {
            CGPathRelease(myPath);
        }
    }
}

这将最终释放此路径并修复内存泄漏。此代码适用于所有iOS版本,包括7.1及以前的版本,并且一旦苹果公司最终修复了这个错误并SKPhysicsBoy实际释放了路径(他们应该这样做),它也应该适用于更高版本。


这是我迄今为止见过的最佳解决方案。已经测试、验证并实施:https://github.com/Tantas/SKShapeNodeLeakFree。 - user2821144
更新:对于iOS版本> 7.1,似乎已经解决了这个问题。如果您有更高版本,请通过检查目标设备的iOS版本来避免调用CFRetain([self getPhysicsBodyPath])。其余部分仍将按照描述的方式工作。 - Tommy356

2

我也遇到过类似的内存泄漏问题,但是在我从viewDidLoad函数中删除了一行代码后,问题得以解决。
skView.showsPhysics = YES;


2

path参数的唯一限制是:

一个凸多边形路径,逆时针旋转且没有自相交。这些点是相对于所属节点的原点指定的。

我不知道offsetXoffsetY的值,因此我不知道路径是否正确,但假设它们都为0,我觉得这条路径是顺时针而不是逆时针的。我将使用常量而非变量创建路径,以确保其正确性,如果仍然泄漏,则说明这是PhysicsKit中的错误。


嗯...我会再次检查路径,但我知道这些规则,我很确定我正确地创建了这个路径。这个凸多边形路径的主体表现得很正常,就像它应该的那样,内存泄漏是我注意到的唯一问题。 - Darrarski
@Darrarski,如果您按照规范使用框架并且在框架中出现泄漏,则框架存在错误。即使是苹果的开发人员也不是完美的。它是否泄漏很多,还是您可以接受? - Erik B

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