内存泄漏(ARC)

时间:2014-09-01 18:22:20

标签: ios objective-c memory-management memory-leaks

在我的previous question的后续行动中,我断定我确实有内存泄漏。总而言之,内存从9.7MB开始,每10次动画运行上升0.1MB,或者看起来如此。我测试了这个大约12MB。

使用Instruments,我运行的测试包括:

  1. 注册初始代
  2. 运行动画10次
  3. 注册另一代
  4. 重复几次
  5. 这是我得到的:

    enter image description here

    因此内存 增长。然而,检查这些世代似乎我不负责这些泄漏。例如,检查“统计”面板,列出的类别似乎表示CF,CG,NS等,Malloc__NSMallocBlock__

    我还检查了Call Trees并跟踪了内存消耗最高的分支。

    enter image description here

    同样,大多数内存消耗似乎与CoreGraphics相关。在Allocations List中,我可以更清楚地看到那些Mallocs是什么。结论是一样的。

    enter image description here enter image description here

    提供完整的源代码是不切实际的,因为应用程序已经达到了几千行。因此,我将概述一些看起来很重要的内容:

    - (void)animateViewDidAppear
    {
        NSArray * buttons = @[self.registrationButton, self.facebookButton, self.twitterButton, self.linkedInButton];
    
        // [...] A bunch of GLfloat calculations here
    
        __block HyAnimationCollection * collection = [[HyAnimationCollection alloc] init];
    
        for (int it=0 ; it < [buttons count] ; ++it) {
    
            UIButton * button = [buttons objectAtIndex:it];
    
            // [...] More GLfloat stuff
    
            // Ease out back
            __block HyAnimation * easeOutBack = [[HyAnimation alloc] init];
            HyAnimationUpdateFunction easeOutBackUpdate = ^(id frame, BOOL done) {
                [button setFrame:[frame CGRectValue]];
            };
    
            [easeOutBack setDelay:it * kHyAuthenticationViewControllerAnimationDelayFactor];
            [easeOutBack setDuration:kHyAuthenticationViewControllerAnimationDuration];
            [easeOutBack setEasing:^(GLfloat t) { return [HyEasing easeOutBack:t]; }];
            [easeOutBack addRectAnimation:CGRectMake(origin.x, from, size.width, size.height)
                                       to:CGRectMake(origin.x, to, size.width, size.height)];
            [easeOutBack addUpdateFunction:easeOutBackUpdate];
            [collection addAnimation:easeOutBack];
        }
    
        [collection addLock:self.animationLock];
        [collection start];
    }
    

    self.animationLock是一种锁定机制,因此动画不会重叠,但它几乎是自包含的,我无法想象为什么它会成为泄漏的来源。但是,这些块会被发送到HyAnimation,而HyAnimationCollection又被添加到addUpdateFunction,这让我更加麻烦,这就是我一直关注的地方。总之,也许这些闭包可能会创建一个圆形保留,所以让我们来看看。 HyAnimation中的- (void)addUpdateFunction:(HyAnimationUpdateFunction)update { [self.updateFunctions addObject: update]; } 实际上非常简单:

    self.updateFunctions

    由于NSMutableArrayHyAnimation,它会保留对这些块的强引用。因此,如果未释放HyAnimationCollection,则这些块也不会,这意味着创建它们的初始范围也不是。但是,HyAnimation是在一个方法中声明的,所以到目前为止,我认为没有理由不释放它。

    这就是为什么我认为它应该是因为动画本身,[collection start];的{​​{1}}。这是有趣的部分:

    for (HyAnimation * anim in self.animations) {
        [anim start];
    }
    

    到目前为止一切顺利。这是HyAnimation的{​​{1}}:

    start

    这几乎将运行和委托推迟到- (void)start { [NSTimer scheduledTimerWithTimeInterval:self.delay target:self selector:@selector(scheduleAnimationWithTimer:) userInfo:nil repeats:NO]; // Send an udate notification if ([self shouldUpdateImmediatly]) { [self animateAt:0.0f done:NO]; } } 。但是,这个方法设置了一个重复的计时器,因此它将一直存在直到动画结束(我希望不再进一步)。

    scheduleAnimationWithTimer:

    现在- (void)scheduleAnimationWithTimer:(NSTimer*)timer { NSTimer * scheduled = [NSTimer scheduledTimerWithTimeInterval:self.frameRate target:self selector:@selector(animateWithTimer:) userInfo:nil repeats:YES]; // Trigger immediatly [self setInitialDate:[NSDate date]]; [scheduled fire]; }

    animateWithTimer:

    最后- (void)animateWithTimer:(NSTimer*)timer { NSTimeInterval gone = [[NSDate date] timeIntervalSinceDate:self.initialDate]; GLfloat t = gone / self.duration; BOOL done = gone >= self.duration; // Ease if (self.easing) { t = self.easing(t); } // Make sure the last position is exact. This does not mean that t does not go over 1.0f during the animation, just the end if (done && t > 1.0f) { t = 1.0f; } // Animate [self animateAt:t done:done]; // Finish if (done) { // Stop the timer [timer invalidate]; // Notify completion [self broadcastCompletion]; } }

    animateAt:done:

    也就是说,这最后一个方法调用我之前在- (void)animateAt:(GLfloat)t done:(BOOL)done { for (HyAnimationFunction anim in self.animations) { anim(t, done); } } 中定义的块。

    首先,我认为animateViewDidAppearHyAnimationCollection实例被困在块中,HyAnimation对这些块有强引用。你同意吗?我怎么解决这个问题?我尝试使用HyAnimation来声明这两个变量,但似乎没有效果。

    无论如何,我也无法将Instrument的内存分析与此问题联系起来,这就是为什么这篇文章很长的原因。

    感谢您对我的承诺,我为长篇大论而道歉。

    更新:

    看来我是对的。在我的previous question上关注@Stephen Darlington的帖子后,我覆盖了__block中的dealloc方法。与他的建议相反,我没有设置断点,而是写了一个HyAnimationCollection。从来没有记录任何东西,直到现在。

    NSLog

    我所做的是将另一个属性添加到- (void)dealloc { NSLog(@"dealloced"); } HyAnimation。如果为true,则shouldCleanUpOnCompletion在完成时调用此方法:

    animateWithTimer:

    我立即在控制台上看到了日志,所以肯定有一个保留周期。问题是,我该如何解决?是不是- (void)cleanUp { // Get rid of everything self.animations = [[NSMutableArray alloc] init]; self.updateFunctions = [[NSMutableArray alloc] init]; self.completionFunctions = [[NSMutableArray alloc] init]; } 应该解决这个问题?

    更新2

    我刚刚意识到这已经足够了:

    __block

    这意味着- (void)cleanUp { // Get rid of everything // self.animations = [[NSMutableArray alloc] init]; // self.updateFunctions = [[NSMutableArray alloc] init]; self.completionFunctions = [[NSMutableArray alloc] init]; } 毕竟是创建闭包的那个。我目前使用的唯一地方是completionFunctions,更具体地说是:

    HyAnimationCollection

    也就是说,保留周​​期必须在这里,对吗?但是哪里?它可能是第一个- (BOOL)addAnimation:(HyAnimation*)animation { @synchronized(self) { if (self.isRunning) { return NO; } [self.animations addObject:animation]; __block HyAnimationCollection * me = self; // Self-subscribe for updates so we know when the animations end [animation addCompletionFunction:^(HyAnimation * anim) { static unsigned int complete = 0; // We are only interested in knowing when the animations complete, so we can release the locks ++complete; if (complete == [me.animations count]) { // Reset, so the animation can be run again complete = 0; @synchronized(me) { // Release all locks [me.locks setLocked:NO]; // Done me.isRunning = NO; } } }]; return YES; } } 块吗?

    更新

    @synchronized allover替换__block似乎已经成功了。即使没有__weak,对象也会释放,但是不正确。首先,它会发布cleanUp,然后发布HyAnimationCollection,最后发布所有HyLockCollectionHyAnimation不应该被释放,因为我在属性中对它进行了强有力的引用。

    让我们再看看HyLock的{​​{1}}

    HyAnimationCollection

    问题是只有在动画完成时才会调用此闭包。因为addAnimation:是第一个被释放的,这意味着当所有动画完成后,- (BOOL)addAnimation:(HyAnimation*)animation { @synchronized(self) { if (self.isRunning) { return NO; } [self.animations addObject:animation]; // Prevent a strong circular reference __weak HyAnimationCollection * me = self; // Self-subscribe for updates so we know when the animations end [animation addCompletionFunction:^(HyAnimation * anim) { static unsigned int complete = 0; // We are only interested in knowing when the animations complete, so we can release the locks ++complete; if (complete == [me.animations count]) { // Reset, so the animation can be run again complete = 0; @synchronized(me) { // Release all locks [me.locks setLocked:NO]; // Done me.isRunning = NO; } } }]; return YES; } } 已经被释放,正如您所看到的那样,导致它不释放锁。

    现在我有相反的问题=)编码非常有趣&lt; 3

1 个答案:

答案 0 :(得分:0)

嗯,问题似乎是这样的:

  • 如果您在self内的块中有-addAnimation:的强引用,则会获得保留周期
  • 如果您在self内的块中对-addAnimation:的引用较弱,则self过早发布,而您的完成块无意义。

您需要的是一种在运行时中断保留周期的方法:

// Version 1:
- (BOOL)addAnimation:(HyAnimation*)animation
{
    @synchronized(self) {

        if (self.isRunning) {
            return NO;
        }

        [self.animations addObject:animation];

        // Self-subscribe for updates so we know when the animations end
        [animation addCompletionFunction:^(HyAnimation * anim) {

            static unsigned int complete = 0;

            // We are only interested in knowing when the animations complete, so we can release the locks
            ++complete;

            if (complete == [self.animations count]) {

                // Reset, so the animation can be run again
                complete = 0;

                @synchronized(self) {

                    // Release all locks
                    [self.locks setLocked:NO];

                    // Break retain cycle!!
                    [self.animations removeAllObjects];

                    // IF it still doesn't work, put a breakpoint at THIS LINE, and
                    // tell me if this code here runs ever.

                    // Done
                    self.isRunning = NO;
                }
            }
        }];

        return YES;
    }
}
  1. self指向数组
  2. 数组指向animation
  3. animation指向一个块
  4. 该块指向self
  5. 当其中一个连接断开时,整个循环就会中断。打破循环的最简单方法是通过调用数组上的-removeAllObjects来销毁数组指针(1)。


    BTW,您的代码的另一个问题是static中的HyAnimationCollection变量。只要同时运行多个HyAnimationCollection对象,这就成了问题。我只想创建一个实例变量unsigned int _completeCount;

    (你也可以创建一个合成属性,但在这种情况下我更喜欢iVar:

    @interface HyAnimationCollection .....
    {
        unsigned int _completeCount; //is automatically initialized to 0 by the objc-runtime.
    }
    
    ...
    

    并在实施文件中:

    // Version 2:
    - (BOOL)addAnimation:(HyAnimation*)animation
    {
        @synchronized(self) {
    
            if (self.isRunning) {
                return NO;
            }
    
            [self.animations addObject:animation];
    
            // Self-subscribe for updates so we know when the animations end
            [animation addCompletionFunction:^(HyAnimation * anim) {
                @synchronized(self) {
                    // We are only interested in knowing when the animations complete, so we can release the locks
                    ++_completeCount;
    
                    if (_completeCount == [self.animations count]) {
    
                        // Reset, so the animation can be run again
                        _completeCount = 0;
    
                        // Release all locks
                        [self.locks setLocked:NO];
    
                        // Break retain cycle!!
                        NSLog(@"Breaking retain cycle (HyAnimationCollection)");
                        [self.animations removeAllObjects];
    
                        // Done
                        self.isRunning = NO;
                    }
                }
            }];
    
            return YES;
        }
    }
    

    2:如果需要,内部@synchronized块应该围绕整个块包裹。只是把它放在解锁周围并不能提供任何线程安全性。 (为了测试它,您还可以将行assert([NSThread isMainThread]);放在该块的开头:如果它没有崩溃,则意味着您可以省略整个@synchronized(self)事物。然后代码变为: )外部@synchronized块可能应该停留。

    // Version 3.
    - (BOOL)addAnimation:(HyAnimation*)animation
    {
        @synchronized(self) {
    
            if (self.isRunning) {
                return NO;
            }
    
            [self.animations addObject:animation];
    
            // Self-subscribe for updates so we know when the animations end
            [animation addCompletionFunction:^(HyAnimation * anim) {
                assert([NSThread isMainThread]);
                // We are only interested in knowing when the animations complete, so we can release the locks
                ++_completeCount;
    
                if (_completeCount == [self.animations count]) {
    
                    // Reset, so the animation can be run again
                    _completeCount = 0;
    
                    // Release all locks
                    [self.locks setLocked:NO];
    
                    // Break retain cycle!!
                    NSLog(@"Breaking retain cycle (HyAnimationCollection)");
                    [self.animations removeAllObjects];
    
                    // Done
                    self.isRunning = NO;
                }
            }];
    
            return YES;
        }
    }
    

    我的提示:首先尝试第3版。如果它崩溃使用版本2而不是。 (该方案应该是DEBUG,而不是RELEASE在开发过程中,否则assert()可能无效,你可能会在以后遇到奇怪的问题。)希望它有效...