0

I've run into what I believe is a bug with UICollisionBehavior in UIKit. Adding them to an array of UIViews leads to a memory leak. I put together a simple demo project that creates 10 animations of a group of views falling with gravity applied, and a collision with the enclosing view's bounds. (Code below.) The Leaks template in Instruments reports nine 64-byte leak for each run.

- (void)doAnimation
{
    self.animateButton.enabled = NO;

    CGFloat left = 12.0f;
    NSMutableArray *items = [NSMutableArray new];

    // set up an array of views and add them to the superview
    while (left < self.view.bounds.size.width - 12.0f) {
        UIView *view = [[UIView alloc] initWithFrame:CGRectMake(left, 70, 32, 32)];
        left += 34.0f;
        [self.view addSubview:view];
        view.backgroundColor = [UIColor grayColor];
        [items addObject:view];
    }

    // create a gravityBehavior and initialize with views array
    UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:items];
    [self.animator addBehavior:gravity];

    // create a collisionBehavior and initialize with views array
    UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:items];
    collision.translatesReferenceBoundsIntoBoundary = YES;
    [self.animator addBehavior:collision];
}

// UIDynamicAnimatorDelegate method that's called when collision animation is complete
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator
{
    // get a collision behavior in order to access its items for loop below
    UICollisionBehavior *behavior;
    for (UIDynamicBehavior *oneBehavior in animator.behaviors) {
        if ([oneBehavior isKindOfClass:[UICollisionBehavior class]]) {
            behavior = (UICollisionBehavior *)oneBehavior;
            break;
        }
    }

    // reset the UIDynamicAnimator property's behaviors for next run
    [self.animator removeAllBehaviors];

    self.dropCount++;

    // remove all subviews
    for (UIView *view in behavior.items) {
         [view removeFromSuperview];
    }

    // run the animation again or break
    if (self.dropCount < 10) {
        [self doAnimation];
    } else {
        self.animateButton.enabled = YES;
    }
}

I'd really like to be able to implement collisions in an app I'm working on, but this leak makes it unusable. I have tried saving the collisionBehavior in property and reusing it. That prevents leaking all but one 64-byte chunk of memory, but the collisions no longer work when that's done. Can anyone suggest a workaround that works?

Unheilig
  • 16,196
  • 193
  • 68
  • 98
Cosmo
  • 171
  • 1
  • 8
  • What is the type of allocation that Instruments reports is leaking? Does the Xcode analyzer indicate a leak in your code? Use the Product > Analyze menu item. Note that both the `items` array and `self.view` are retaining the new `view` instances you are creating in `-doAnimation`. If you are using manual reference counting instead of ARC, you must release your `items` array and your `view` instances within the `while` loop. – bneely Jan 31 '14 at 20:05
  • This is using ARC, so it should automatically handle retain and release. The Instruments template is reporting a Malloc event type, but I don't see any other types called out (sorry if I'm not understanding your question properly). It points to PhysicsKit as the responsible library, and the responsible caller as std::__1::__split_buffer&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator&). – Cosmo Jan 31 '14 at 21:21
  • You should be able to create a test project and run it yourself with just a little bit of extra code if you'd like to see for yourself in Instruments. You'd need to add UIDynamicAnimator *animator and NSInteger dropCount properties, then call doAnimation from ViewDidLoad in a new empty project. – Cosmo Jan 31 '14 at 21:25

1 Answers1

0

I ran into the same issue but fixed the memory leak by setting the boundaries using UICollisionBehavior's

addBoundaryWithIdentifier

Unfortunately setting boundaries with translatesReferenceBoundsIntoBoundary and setTranslatesReferenceBoundsIntoBoundaryWithInsets caused leaks for me.

glued
  • 2,579
  • 1
  • 25
  • 40
  • 1
    I found that using addBoundaryWithIdentifier:fromPoint:toPoint: eliminated the leak. At first I tried the addBoundaryWithIdentifier:forPath: method, but it was apparently retaining the bezier path objects for each collision object I added it to. I used a single bezier path stored in a property in an attempt to limit the leaking problem, but that didn't help. Using CGPoints, which are clearly not retained, did the trick. Thanks for pointing me in the right direction. – Cosmo May 06 '14 at 18:49