简体   繁体   English

内存泄漏(ARC)

[英]Memory leak (ARC)

In the follow up of my previous question , I concluded that I do have a memory leak. 上一个问题的后续中,我得出的结论是确实存在内存泄漏。 To sum up, memory starts at 9.7MB and goes up 0.1MB every 10 runs of the animation, or so it seems. 总而言之,内存开始时为9.7MB,每运行10次动画就增加0.1MB,或者看起来是这样。 I tested this to about 12MB. 我测试了大约12MB。

Using Instruments, I run a test that consisted of: 使用Instruments,我运行了一个包含以下内容的测试:

  1. Register an initial generation 注册初代
  2. Run the animation 10 times 运行动画10次
  3. Register another generation 注册另一代
  4. Repeat a few times 重复几次

Here's what I got: 这是我得到的:

在此处输入图片说明

So the memory does grow. 因此内存确实会增加。 However, inspecting these generations it seems that I'm not responsible for these leaks. 但是,检查这些世代似乎对这些泄漏不承担任何责任。 For instance, inspecting the Statistics panel, listed categories seem to indicate CF, CG, NS, etc, and Malloc and __NSMallocBlock__ . 例如,检查“统计信息”面板时,列出的类别似乎指示CF,CG,NS等,以及Malloc__NSMallocBlock__

I also inspected the Call Trees and followed the branches with highest memory consumption. 我还检查了调用树,并跟踪了具有最高内存消耗的分支。

在此处输入图片说明

Again, most memory consumption seems to be CoreGraphics related. 同样,大多数内存消耗似乎与CoreGraphics有关。 In Allocations List, I can see more clearly what those Mallocs are. 在分配列表中,我可以更清楚地看到这些Malloc是什么。 The conclusion is the same. 结论是相同的。

在此处输入图片说明在此处输入图片说明

Providing full source code would not be practical as the application already reached a few thousands of lines. 提供完整的源代码是不切实际的,因为该应用程序已经达到了数千行。 As such, I'll give an overview of what seems to be important: 因此,我将概述似乎很重要的内容:

- (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 is a locking mechanism so animations don't overlap, but it's pretty much self contained and I can't imagine why it would be the source of the leak. self.animationLock是一种锁定机制,因此动画不会重叠,但是它几乎是自包含的,我无法想象为什么会导致泄漏。 However, those blocks do get sent to HyAnimation , which in turn is added to HyAnimationCollection , and that troubles me more and that's where I have been focusing. 但是,这些块确实被发送到HyAnimation ,后者又被添加到HyAnimationCollection ,这使我更加烦恼,这就是我一直关注的地方。 In sum, maybe these closures might be creating a circular retain, so lets take a look. 总而言之,也许这些闭包可能正在创建圆形保留,所以让我们看一下。 The addUpdateFunction in HyAnimation is actually very simple: HyAnimationaddUpdateFunction实际上非常简单:

- (void)addUpdateFunction:(HyAnimationUpdateFunction)update
{
    [self.updateFunctions addObject: update];
}

As self.updateFunctions is an NSMutableArray , it retains strong references to these blocks. 由于self.updateFunctionsNSMutableArray ,它保留了对这些块的强引用。 So if HyAnimation is not freed, neither are those blocks, which means that the initial scope in which they were created isn't either. 因此,如果不释放HyAnimation ,则这些块也不会释放,这意味着创建它们的初始范围也不会释放。 But, HyAnimation is declared inside a method so, so far, I see no reason why it wouldn't be released. 但是,HyAnimation是在方法内部声明的,所以到目前为止,我仍然没有理由不发布它。

Which is why I think it should be because of the animation itself, that is HyAnimationCollection 's [collection start]; 这就是为什么我认为应该是因为动画本身,即HyAnimationCollection[collection start]; . Here's the interesting part: 这是有趣的部分:

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

So far so good. 到现在为止还挺好。 Here's HyAnimation 's start : 这是HyAnimationstart

- (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];
    }
}

Which pretty much delays the run and delegates to scheduleAnimationWithTimer: . 这几乎会延迟运行,并委托scheduleAnimationWithTimer: This method, however, sets a timer that repeats, and therefore will be around until the animation ends (and no further, I hope). 但是,此方法设置了一个重复的计时器,因此将一直持续到动画结束为止(我希望不要再重复了)。

- (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];
}

Now animateWithTimer: 现在设置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];
    }
}

And finally animateAt:done: 最后是animateAt:done:

- (void)animateAt:(GLfloat)t done:(BOOL)done
{
    for (HyAnimationFunction anim in self.animations) {
        anim(t, done);
    }
}

That is, this last method calls the blocks I defined earlier in animateViewDidAppear . 也就是说,这最后一个方法调用了我先前在animateViewDidAppear定义的块。

First of all, I believe that the HyAnimationCollection and HyAnimation instances are trapped inside the blocks, and HyAnimation has strong references to those blocks. 首先,我相信HyAnimationCollectionHyAnimation实例被困在块内,并且HyAnimation对这些块有很强的引用。 Would you agree? 你同意吗? How can I solve this? 我该如何解决? I tried using __block to declare both variables, but it seems to have no effect, for this matter. 我尝试使用__block声明两个变量,但是对于这件事似乎没有任何作用。

Anyway, I'm also having trouble relating Instrument's memory analysis with this issue, which is why this post is so long. 无论如何,我在将此问题与Instrument的内存分析相关时也遇到了麻烦,这就是为什么这篇文章这么长的原因。

Thank you for bearing with me, and I apologise for the long read. 多谢您与我保持联系,对您的长时间阅读深表歉意。

UPDATE: 更新:

It seems I was right. 看来我是对的。 Following @Stephen Darlington's post on my previous question , I overrode the dealloc method in HyAnimationCollection . 在@Stephen Darlington关于我之前的问题的帖子之后,我覆盖了HyAnimationCollectiondealloc方法。 Contrary to his suggestion, I did not setup a break point, but instead wrote an NSLog . 与他的建议相反,我没有设置断点,而是编写了NSLog Never logged anything, until now. 直到现在都没有记录任何东西。

- (void)dealloc
{
    NSLog(@"dealloced");
}

What I did was add another property to HyAnimation , shouldCleanUpOnCompletion . 我所做的是在HyAnimation添加了另一个属性shouldCleanUpOnCompletion If true, animateWithTimer: calls this on completion: 如果为true,则animateWithTimer:在完成时调用此方法:

- (void)cleanUp
{
    // Get rid of everything
    self.animations = [[NSMutableArray alloc] init];
    self.updateFunctions = [[NSMutableArray alloc] init];
    self.completionFunctions = [[NSMutableArray alloc] init];
}

I immediately saw logs on the console, so there's definitely a retain cycle. 我立即在控制台上看到日志,因此肯定存在保留周期。 The question is, how can I solve it? 问题是,我该如何解决? Isn't __block supposed to solve this?? __block应该解决吗?

UPDATE 2 更新2

I just realised this is enough: 我刚刚意识到这已经足够:

- (void)cleanUp
{
    // Get rid of everything
//    self.animations = [[NSMutableArray alloc] init];
//    self.updateFunctions = [[NSMutableArray alloc] init];
    self.completionFunctions = [[NSMutableArray alloc] init];
}

Which means that the completionFunctions are the ones creating the closure, after all. 毕竟,这意味着completionFunctions是创建闭包的功能。 The only place I'm currently using those is in HyAnimationCollection , more specifically here: 我当前使用的唯一位置是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;
    }
}

That is, the retain cycle must be here, right? 也就是说,保留周​​期必须在这里,对吗? But where? 但是哪里? Could it be the first @synchronized block? 可以是第一个@synchronized块吗?

UPDATE 更新

Replacing __block with __weak allover seems to have done the trick. __weak代替__block似乎已经成功了。 Even without cleanUp the objects do release, however not properly. 即使没有cleanUp ,对象也会释放,但是不能正确释放。 First, it releases HyAnimationCollection , then HyLockCollection and finally all HyAnimation s. 首先,它会释放HyAnimationCollection ,然后HyLockCollection最后全部HyAnimation秒。 HyLock is not supposed to be released because I keep it a strong reference to it in a property. HyLock不应该发布,因为我在属性中始终强烈引用它。

Lets take another look at HyAnimationCollection 's addAnimation: 让我们再看一下HyAnimationCollectionaddAnimation:

- (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;
    }
}

The problem is that this closure is only called when animations finish. 问题在于仅在动画结束时才调用此闭包。 Because HyAnimationCollection is the first to be released, this means that when all animations finish, HyAnimationCollection has already been released, which, as you can see, causes it not to release the locks. 因为HyAnimationCollection是第一个发布的,所以这意味着当所有动画结束时, HyAnimationCollection已经发布,如您所见,这导致它不释放锁。

Now I have quite the opposite problem =) coding is so much fun <3 现在我遇到了相反的问题=)编码非常有趣<3

Well, the problem seems to be this: 好吧,问题似乎是这样的:

  • if you have a strong reference to self in your block inside -addAnimation: , you get a retain-cycle 如果您在-addAnimation:中的块中强烈引用了self ,则会得到一个保留周期
  • if you have a weak reference to self in your block inside -addAnimation: , self is released too early, and your completion block is pointless. 如果您在-addAnimation:中的代码块中对self引用较弱,那么self被过早释放,而您的完成代码块将毫无意义。

What you need is a way to break the retain-cycle at runtime: 您需要的是一种在运行时打破保留周期的方法:

// 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 points to an array self指向数组
  2. the array points to an animation 数组指向animation
  3. the animation points to a block animation指向一个块
  4. the block points to self 障碍指向self

When one of these connections is broken, the whole cycle breaks. 当这些连接之一断开时,整个周期就会中断。 The easiest way to break the cycle is to destroy the array pointer (1) by calling -removeAllObjects on the array. 打破循环的最简单方法是通过在数组上调用-removeAllObjects破坏数组指针(1)。


BTW, another problem with your code is the static variable in your HyAnimationCollection . 顺便说一句,您的代码的另一个问题是HyAnimationCollectionstatic变量。 This will be a problem as soon as you have more than one HyAnimationCollection object running at the same time. 一旦同时运行多个HyAnimationCollection对象,这将是一个问题。 I would just create an instance variable unsigned int _completeCount; 我只是创建一个实例变量unsigned int _completeCount; :

(you could also create a synthesized property instead, but I prefer an iVar in this case: (您也可以创建一个综合属性,但在这种情况下,我更喜欢使用iVar:

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

...

)

and in the implementation file: 并在实现文件中:

// 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.: the inner @synchronized block should be wrapped around the whole block, if it is needed at all. 2 .:如果需要,内部@synchronized块应包装在整个块周围。 Just putting it around the unlocking doesn't provide any thread safety. 只是将其放在解锁周围并不能提供任何线程安全性。 (To test it, you can also put the line assert([NSThread isMainThread]); at the beginning of that block: if it doesn't crash, it means that you can omit the whole @synchronized(self) thing. The code then becomes:) The outer @synchronized block should probably stay. (要对其进行测试,您还可以在该块的开头放置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;
    }
}

My tip: try version 3 first. 我的提示:首先尝试版本3。 if it crashes use version 2 instead. 如果崩溃,请改用版本2。 (the scheme should be DEBUG, not RELEASE during development, otherwise assert() may not work and you may get strange problems later.) hope it works... (该方案应该是DEBUG,而不是在开发过程中发布,否则assert()可能无法正常工作,以后您可能会遇到一些奇怪的问题。)希望它能起作用...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM